@adecore/agents-react 0.31.11 → 0.32.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 (404) hide show
  1. package/dist/agents/AccountDot.js +57 -6
  2. package/dist/agents/AgentIcon.js +88 -12
  3. package/dist/agents/ModelOptionControl.js +153 -18
  4. package/dist/agents/ProviderLogo.js +49 -4
  5. package/dist/agents/account-limits.js +17 -14
  6. package/dist/agents/accounts.js +99 -67
  7. package/dist/agents/agent-marks.js +5 -3
  8. package/dist/agents/model-name.js +42 -31
  9. package/dist/agents/model-options.js +8 -8
  10. package/dist/agents/model-requirement.js +17 -14
  11. package/dist/agents/option-words.d.ts +7 -0
  12. package/dist/agents/option-words.js +35 -0
  13. package/dist/agents/provider-paths.js +6 -6
  14. package/dist/agents/status-look.js +32 -12
  15. package/dist/chat/account-choice.d.ts +13 -0
  16. package/dist/chat/account-choice.js +90 -20
  17. package/dist/chat/actions.js +126 -59
  18. package/dist/chat/activity.js +5 -5
  19. package/dist/chat/attachments.js +74 -53
  20. package/dist/chat/bookmarks.js +58 -48
  21. package/dist/chat/chat-client.js +344 -263
  22. package/dist/chat/chat-references.js +3 -3
  23. package/dist/chat/composer-effort.d.ts +5 -0
  24. package/dist/chat/composer-effort.js +26 -0
  25. package/dist/chat/composer-send.js +13 -13
  26. package/dist/chat/drafts.js +115 -79
  27. package/dist/chat/forks.js +81 -24
  28. package/dist/chat/guards.js +15 -43
  29. package/dist/chat/index.d.ts +3 -1
  30. package/dist/chat/index.js +3 -2
  31. package/dist/chat/logic/ansi.js +170 -167
  32. package/dist/chat/logic/answers.js +2 -2
  33. package/dist/chat/logic/bookmarks.js +19 -19
  34. package/dist/chat/logic/command-looks.js +138 -96
  35. package/dist/chat/logic/context-usage.js +13 -9
  36. package/dist/chat/logic/continue-after-stop.js +15 -19
  37. package/dist/chat/logic/effort.d.ts +14 -0
  38. package/dist/chat/logic/effort.js +34 -0
  39. package/dist/chat/logic/embedded-code.js +387 -326
  40. package/dist/chat/logic/fork.js +134 -110
  41. package/dist/chat/logic/generated-image.js +54 -23
  42. package/dist/chat/logic/goal.js +28 -20
  43. package/dist/chat/logic/handback.js +6 -6
  44. package/dist/chat/logic/json.js +2 -2
  45. package/dist/chat/logic/limit.js +46 -30
  46. package/dist/chat/logic/picker-entries.d.ts +5 -3
  47. package/dist/chat/logic/picker-entries.js +83 -43
  48. package/dist/chat/logic/resume-compaction.d.ts +14 -1
  49. package/dist/chat/logic/resume-compaction.js +93 -24
  50. package/dist/chat/logic/scrubber.js +188 -136
  51. package/dist/chat/logic/thought.js +13 -16
  52. package/dist/chat/logic/thread-cards.js +60 -48
  53. package/dist/chat/logic/timeline-copy.js +30 -38
  54. package/dist/chat/logic/timeline-target.js +92 -63
  55. package/dist/chat/logic/timeline.js +499 -355
  56. package/dist/chat/logic/tool-catalog.js +54 -34
  57. package/dist/chat/logic/tools.js +106 -82
  58. package/dist/chat/logic/visual-height.js +18 -18
  59. package/dist/chat/logic/visual-viewport.js +6 -3
  60. package/dist/chat/logic/welcome.js +9 -9
  61. package/dist/chat/logic/workflow.js +45 -34
  62. package/dist/chat/mentions.js +149 -109
  63. package/dist/chat/persisted-json.js +51 -54
  64. package/dist/chat/preferences.d.ts +4 -0
  65. package/dist/chat/preferences.js +162 -84
  66. package/dist/chat/prompt-hold.js +9 -9
  67. package/dist/chat/quote.js +21 -30
  68. package/dist/chat/reading-width.js +12 -10
  69. package/dist/chat/recent-messages.js +34 -21
  70. package/dist/chat/resume-compaction-dismissals.js +25 -5
  71. package/dist/chat/runtime-modes.js +3 -3
  72. package/dist/chat/send-key.js +11 -7
  73. package/dist/chat/stash.js +89 -69
  74. package/dist/chat/subagent-conversation.js +181 -139
  75. package/dist/chat/subagent-list.js +65 -67
  76. package/dist/chat/subagent-support.js +12 -7
  77. package/dist/chat/subagent-view.js +132 -44
  78. package/dist/chat/thumbnails.js +88 -60
  79. package/dist/chat/timeline-flash.js +24 -12
  80. package/dist/chat/timeline-scroll.js +116 -86
  81. package/dist/chat/ui/AccountPill.js +126 -15
  82. package/dist/chat/ui/AnsiOutput.js +62 -6
  83. package/dist/chat/ui/BookmarkMarker.js +231 -41
  84. package/dist/chat/ui/BookmarkSubmenu.js +165 -18
  85. package/dist/chat/ui/ChatActivity.js +649 -70
  86. package/dist/chat/ui/ChatReferenceChip.js +104 -9
  87. package/dist/chat/ui/CodeBlock.js +132 -58
  88. package/dist/chat/ui/Composer.js +866 -132
  89. package/dist/chat/ui/ComposerInput.js +309 -120
  90. package/dist/chat/ui/DiffPool.js +75 -14
  91. package/dist/chat/ui/EditDiff.js +160 -21
  92. package/dist/chat/ui/FadingWords.js +32 -3
  93. package/dist/chat/ui/GoalDock.js +235 -42
  94. package/dist/chat/ui/ImageView.js +617 -61
  95. package/dist/chat/ui/LimitState.js +446 -72
  96. package/dist/chat/ui/Markdown.js +991 -173
  97. package/dist/chat/ui/MermaidBlock.js +238 -68
  98. package/dist/chat/ui/MessageActions.js +251 -39
  99. package/dist/chat/ui/ModelMarks.js +55 -5
  100. package/dist/chat/ui/Pickers.js +664 -113
  101. package/dist/chat/ui/PromptComposer.js +281 -51
  102. package/dist/chat/ui/QuoteButton.js +151 -57
  103. package/dist/chat/ui/ResumeCompactionDock.d.ts +6 -2
  104. package/dist/chat/ui/ResumeCompactionDock.js +141 -6
  105. package/dist/chat/ui/RowTime.js +59 -5
  106. package/dist/chat/ui/RunSettings.js +1744 -154
  107. package/dist/chat/ui/Scrubber.js +517 -88
  108. package/dist/chat/ui/ShellCommand.js +183 -107
  109. package/dist/chat/ui/SubagentControls.js +212 -24
  110. package/dist/chat/ui/SubagentInfo.js +170 -13
  111. package/dist/chat/ui/SubagentStopButton.js +115 -24
  112. package/dist/chat/ui/SubagentTimeline.js +357 -91
  113. package/dist/chat/ui/Timeline.js +988 -476
  114. package/dist/chat/ui/TimelineMenu.js +552 -37
  115. package/dist/chat/ui/UnifiedDiff.js +234 -27
  116. package/dist/chat/ui/UploadThumb.js +37 -7
  117. package/dist/chat/ui/VisualDialogs.js +251 -20
  118. package/dist/chat/ui/VisualFrame.js +321 -95
  119. package/dist/chat/ui/Welcome.js +72 -7
  120. package/dist/chat/ui/chips.js +1 -1
  121. package/dist/chat/ui/code-lines.js +36 -36
  122. package/dist/chat/ui/code-streaming.js +1 -1
  123. package/dist/chat/ui/code-theme.js +7 -7
  124. package/dist/chat/ui/composer/ComposerControls.js +329 -21
  125. package/dist/chat/ui/composer/ComposerMenu.js +358 -30
  126. package/dist/chat/ui/composer/DraftParts.js +185 -11
  127. package/dist/chat/ui/composer/QueuedMessages.js +138 -13
  128. package/dist/chat/ui/composer/chips.js +29 -19
  129. package/dist/chat/ui/composer/editor.js +79 -63
  130. package/dist/chat/ui/composer/insert.js +26 -12
  131. package/dist/chat/ui/composer/key-handler.js +180 -127
  132. package/dist/chat/ui/composer/keys.js +90 -73
  133. package/dist/chat/ui/composer/menu-aria.js +18 -9
  134. package/dist/chat/ui/composer/placeholder.js +78 -31
  135. package/dist/chat/ui/composer/surface.js +13 -37
  136. package/dist/chat/ui/composer/use-chat-controls.js +230 -90
  137. package/dist/chat/ui/composer/use-compaction-offer.js +50 -18
  138. package/dist/chat/ui/composer/use-composer-draft.js +65 -34
  139. package/dist/chat/ui/composer/use-composer-menus.js +478 -193
  140. package/dist/chat/ui/composer/use-composer-submit.js +251 -109
  141. package/dist/chat/ui/composer/use-continuable-turn.js +33 -6
  142. package/dist/chat/ui/composer/use-draft-files.js +245 -93
  143. package/dist/chat/ui/composer/use-effort-stepper.d.ts +13 -0
  144. package/dist/chat/ui/composer/use-effort-stepper.js +88 -0
  145. package/dist/chat/ui/composer/use-notice.js +61 -16
  146. package/dist/chat/ui/composer/use-pending-requests.js +38 -17
  147. package/dist/chat/ui/composer/use-prompt-history.js +117 -45
  148. package/dist/chat/ui/composer/use-prompt-hold.js +119 -35
  149. package/dist/chat/ui/composer/use-take-back.js +94 -72
  150. package/dist/chat/ui/diff/ApprovalReview.js +101 -23
  151. package/dist/chat/ui/diff/DiffFallbackBoundary.js +21 -13
  152. package/dist/chat/ui/diff/DiffOmitted.js +56 -6
  153. package/dist/chat/ui/diff/DiffStack.js +92 -8
  154. package/dist/chat/ui/diff/DiffUnavailable.js +56 -6
  155. package/dist/chat/ui/diff/EditorEditDiff.js +263 -51
  156. package/dist/chat/ui/diff/EditorPatchDiff.js +39 -14
  157. package/dist/chat/ui/diff/LegacyDiffPool.js +27 -10
  158. package/dist/chat/ui/diff/LegacyEditDiff.js +97 -20
  159. package/dist/chat/ui/diff/LegacyUnifiedDiff.js +172 -42
  160. package/dist/chat/ui/diff/RawDiff.js +65 -5
  161. package/dist/chat/ui/diff/ReviewDiff.js +77 -5
  162. package/dist/chat/ui/diff/diff-props.js +1 -1
  163. package/dist/chat/ui/diff/diff-source-budget.js +46 -35
  164. package/dist/chat/ui/diff/editor-edit-model.js +18 -14
  165. package/dist/chat/ui/diff/review-plan.js +63 -40
  166. package/dist/chat/ui/diff/review-settle.js +24 -9
  167. package/dist/chat/ui/diff/syntax-runtime.js +236 -201
  168. package/dist/chat/ui/diff/use-review-stack.js +146 -25
  169. package/dist/chat/ui/diff-syntax.js +70 -58
  170. package/dist/chat/ui/diff-theme.js +31 -12
  171. package/dist/chat/ui/file-links.js +39 -10
  172. package/dist/chat/ui/find-reveal.js +29 -8
  173. package/dist/chat/ui/full-diff.js +17 -12
  174. package/dist/chat/ui/highlighter.js +143 -113
  175. package/dist/chat/ui/icons.js +16 -7
  176. package/dist/chat/ui/index.js +1 -1
  177. package/dist/chat/ui/intelligent-ui/UiBlockFrame.js +556 -82
  178. package/dist/chat/ui/intelligent-ui/UiFallbackPart.js +73 -5
  179. package/dist/chat/ui/intelligent-ui/UiReply.js +666 -262
  180. package/dist/chat/ui/intelligent-ui/block-local.js +59 -31
  181. package/dist/chat/ui/intelligent-ui/chart-data.js +77 -67
  182. package/dist/chat/ui/intelligent-ui/node-text.js +77 -91
  183. package/dist/chat/ui/intelligent-ui/registry.js +46 -46
  184. package/dist/chat/ui/intelligent-ui/render-context.js +1 -1
  185. package/dist/chat/ui/intelligent-ui/renderers/base-land.js +1 -1
  186. package/dist/chat/ui/intelligent-ui/renderers/binding.js +3 -3
  187. package/dist/chat/ui/intelligent-ui/renderers/chart.js +1223 -123
  188. package/dist/chat/ui/intelligent-ui/renderers/choices.js +312 -34
  189. package/dist/chat/ui/intelligent-ui/renderers/content.js +1241 -86
  190. package/dist/chat/ui/intelligent-ui/renderers/data.js +619 -54
  191. package/dist/chat/ui/intelligent-ui/renderers/inputs.js +868 -67
  192. package/dist/chat/ui/intelligent-ui/renderers/links.js +366 -62
  193. package/dist/chat/ui/intelligent-ui/renderers/map-slots.js +118 -60
  194. package/dist/chat/ui/intelligent-ui/renderers/map-style.js +85 -78
  195. package/dist/chat/ui/intelligent-ui/renderers/map-view.js +282 -217
  196. package/dist/chat/ui/intelligent-ui/renderers/map.js +621 -69
  197. package/dist/chat/ui/intelligent-ui/renderers/place-point.js +15 -8
  198. package/dist/chat/ui/intelligent-ui/renderers/structure.js +262 -20
  199. package/dist/chat/ui/intelligent-ui/renderers/text.js +674 -49
  200. package/dist/chat/ui/intelligent-ui/reply-parts.js +24 -15
  201. package/dist/chat/ui/intelligent-ui/table-data.js +97 -57
  202. package/dist/chat/ui/intelligent-ui/tones.js +31 -31
  203. package/dist/chat/ui/intelligent-ui/use-in-view.js +30 -12
  204. package/dist/chat/ui/intelligent-ui/use-later.js +28 -13
  205. package/dist/chat/ui/intelligent-ui/use-truncated.js +45 -21
  206. package/dist/chat/ui/intelligent-ui/use-ui-links.js +296 -109
  207. package/dist/chat/ui/intelligent-ui/use-ui-queries.js +256 -246
  208. package/dist/chat/ui/markdown-blocks.js +296 -262
  209. package/dist/chat/ui/mermaid.js +60 -72
  210. package/dist/chat/ui/quote-selection.js +32 -29
  211. package/dist/chat/ui/rehype-chips.js +35 -23
  212. package/dist/chat/ui/rehype-fade.js +28 -17
  213. package/dist/chat/ui/remark-html-as-text.js +22 -15
  214. package/dist/chat/ui/reply-context.js +1 -1
  215. package/dist/chat/ui/reveal.js +161 -72
  216. package/dist/chat/ui/rows/ForksRow.js +226 -24
  217. package/dist/chat/ui/rows/GeneratedImageRow.js +541 -87
  218. package/dist/chat/ui/rows/MessageRows.js +1706 -159
  219. package/dist/chat/ui/rows/RowContainer.js +57 -7
  220. package/dist/chat/ui/rows/Rows.js +463 -54
  221. package/dist/chat/ui/rows/SubagentRow.js +577 -60
  222. package/dist/chat/ui/rows/TaskRow.js +359 -40
  223. package/dist/chat/ui/rows/VisualRow.js +233 -11
  224. package/dist/chat/ui/rows/WorkRows.js +1946 -163
  225. package/dist/chat/ui/rows/WorkflowRow.js +293 -34
  226. package/dist/chat/ui/rows/row-rhythm.js +27 -17
  227. package/dist/chat/ui/theme-watch.js +26 -23
  228. package/dist/chat/ui/timestamp-format.js +4 -1
  229. package/dist/chat/ui/use-diff-source-tokens.js +70 -34
  230. package/dist/chat/ui/useToggleSet.js +51 -19
  231. package/dist/chat/ui/visual-theme.js +153 -128
  232. package/dist/chat/visual-bridge.js +123 -123
  233. package/dist/chat/visual-viewport.js +94 -83
  234. package/dist/chat/visuals.js +13 -5
  235. package/dist/host-hooks.d.ts +29 -0
  236. package/dist/host-hooks.js +78 -0
  237. package/dist/host.js +120 -48
  238. package/dist/index.js +1 -1
  239. package/dist/lazy.js +38 -32
  240. package/dist/locales/en/agent-chat.d.ts +35 -0
  241. package/dist/locales/en/agent-chat.js +747 -712
  242. package/dist/locales/en/agent-prompts.js +42 -42
  243. package/dist/locales/en/agent-providers.d.ts +1 -0
  244. package/dist/locales/en/agent-providers.js +166 -165
  245. package/dist/locales/en/agent-usage.d.ts +6 -0
  246. package/dist/locales/en/agent-usage.js +129 -123
  247. package/dist/locales/nl/agent-chat.d.ts +35 -0
  248. package/dist/locales/nl/agent-chat.js +747 -712
  249. package/dist/locales/nl/agent-prompts.js +42 -42
  250. package/dist/locales/nl/agent-providers.d.ts +1 -0
  251. package/dist/locales/nl/agent-providers.js +166 -165
  252. package/dist/locales/nl/agent-usage.d.ts +6 -0
  253. package/dist/locales/nl/agent-usage.js +129 -123
  254. package/dist/locales.js +14 -14
  255. package/dist/mounted-registry.js +31 -30
  256. package/dist/port-transport.js +122 -111
  257. package/dist/prompts/index.js +1 -1
  258. package/dist/prompts/logic/focus.js +15 -14
  259. package/dist/prompts/logic/keys.js +93 -59
  260. package/dist/prompts/logic/prompts.js +27 -14
  261. package/dist/prompts/logic/subjects.js +60 -46
  262. package/dist/prompts/logic/useFocusAfterAnswer.js +60 -20
  263. package/dist/prompts/logic/usePromptSession.js +88 -77
  264. package/dist/prompts/ui/ApprovalBody.js +246 -25
  265. package/dist/prompts/ui/PromptActions.js +215 -9
  266. package/dist/prompts/ui/PromptCard.js +215 -36
  267. package/dist/prompts/ui/PromptView.js +372 -34
  268. package/dist/prompts/ui/QuestionBody.js +269 -73
  269. package/dist/providers/AccountColors.js +36 -5
  270. package/dist/providers/AccountDetail.js +683 -50
  271. package/dist/providers/AccountVariables.js +139 -32
  272. package/dist/providers/AddAccountForm.js +125 -38
  273. package/dist/providers/CliDetail.js +343 -36
  274. package/dist/providers/InstallPage.js +644 -59
  275. package/dist/providers/ProviderList.d.ts +2 -1
  276. package/dist/providers/ProviderList.js +215 -23
  277. package/dist/providers/ProvidersPane.d.ts +4 -2
  278. package/dist/providers/ProvidersPane.js +244 -64
  279. package/dist/providers/account-actions.js +54 -30
  280. package/dist/providers/index.js +1 -1
  281. package/dist/providers/install-steps.js +12 -9
  282. package/dist/providers/pane-target.d.ts +3 -0
  283. package/dist/providers/pane-target.js +34 -13
  284. package/dist/providers/parts.js +62 -12
  285. package/dist/providers/provider-abilities.js +5 -5
  286. package/dist/providers/provider-groups.js +75 -40
  287. package/dist/providers/variables.js +73 -39
  288. package/dist/scope.js +6 -6
  289. package/dist/settings/sections.js +18 -18
  290. package/dist/state/chats.d.ts +1 -0
  291. package/dist/state/chats.js +408 -192
  292. package/dist/state/index.js +1 -1
  293. package/dist/state/provider-accounts.js +70 -47
  294. package/dist/state/providers.js +45 -16
  295. package/dist/state/usage.js +154 -77
  296. package/dist/storage.js +69 -68
  297. package/dist/testing/index.js +1 -1
  298. package/dist/testing/prompts.js +105 -67
  299. package/dist/transport.js +10 -10
  300. package/dist/ui/PlainTextarea.js +27 -3
  301. package/dist/usage/LimitsList.js +646 -40
  302. package/dist/usage/UsageBreakdown.js +879 -38
  303. package/dist/usage/UsageChart.js +311 -32
  304. package/dist/usage/UsageDialog.js +46 -4
  305. package/dist/usage/UsageLimits.js +467 -34
  306. package/dist/usage/UsageLimitsCard.js +95 -11
  307. package/dist/usage/UsagePage.js +817 -96
  308. package/dist/usage/UsagePane.js +164 -10
  309. package/dist/usage/UsageSummary.js +151 -6
  310. package/dist/usage/UsageTiles.js +164 -14
  311. package/dist/usage/format.js +32 -32
  312. package/dist/usage/index.d.ts +1 -1
  313. package/dist/usage/index.js +2 -1
  314. package/dist/usage/limit-groups.d.ts +2 -0
  315. package/dist/usage/limit-groups.js +77 -59
  316. package/dist/usage/limits.js +78 -43
  317. package/dist/usage/money.js +20 -4
  318. package/dist/usage/summary.js +85 -64
  319. package/package.json +8 -8
  320. package/src/agents/ModelOptionControl.tsx +3 -1
  321. package/src/agents/option-words.ts +27 -0
  322. package/src/chat/account-choice.ts +41 -1
  323. package/src/chat/composer-effort.ts +32 -0
  324. package/src/chat/forks.ts +4 -5
  325. package/src/chat/index.ts +3 -1
  326. package/src/chat/logic/effort.ts +32 -0
  327. package/src/chat/logic/picker-entries.ts +45 -10
  328. package/src/chat/logic/resume-compaction.ts +95 -17
  329. package/src/chat/logic/timeline.ts +11 -3
  330. package/src/chat/preferences.ts +35 -0
  331. package/src/chat/subagent-view.ts +2 -3
  332. package/src/chat/ui/AnsiOutput.tsx +1 -2
  333. package/src/chat/ui/BookmarkSubmenu.tsx +3 -3
  334. package/src/chat/ui/ChatActivity.tsx +14 -22
  335. package/src/chat/ui/ChatReferenceChip.tsx +2 -3
  336. package/src/chat/ui/Composer.tsx +15 -7
  337. package/src/chat/ui/EditDiff.tsx +2 -3
  338. package/src/chat/ui/LimitState.tsx +2 -2
  339. package/src/chat/ui/Markdown.tsx +10 -8
  340. package/src/chat/ui/MermaidBlock.tsx +2 -3
  341. package/src/chat/ui/Pickers.tsx +49 -29
  342. package/src/chat/ui/PromptComposer.tsx +7 -9
  343. package/src/chat/ui/ResumeCompactionDock.tsx +16 -4
  344. package/src/chat/ui/RunSettings.tsx +67 -28
  345. package/src/chat/ui/Scrubber.tsx +3 -3
  346. package/src/chat/ui/ShellCommand.tsx +2 -0
  347. package/src/chat/ui/SubagentInfo.tsx +2 -3
  348. package/src/chat/ui/SubagentTimeline.tsx +12 -16
  349. package/src/chat/ui/Timeline.tsx +6 -6
  350. package/src/chat/ui/UnifiedDiff.tsx +3 -4
  351. package/src/chat/ui/VisualFrame.tsx +5 -8
  352. package/src/chat/ui/code-theme.ts +4 -4
  353. package/src/chat/ui/composer/placeholder.ts +2 -2
  354. package/src/chat/ui/composer/use-compaction-offer.ts +36 -16
  355. package/src/chat/ui/composer/use-composer-menus.ts +4 -9
  356. package/src/chat/ui/composer/use-composer-submit.ts +2 -1
  357. package/src/chat/ui/composer/use-effort-stepper.ts +56 -0
  358. package/src/chat/ui/composer/use-notice.ts +2 -2
  359. package/src/chat/ui/diff/EditorEditDiff.tsx +9 -13
  360. package/src/chat/ui/diff/LegacyEditDiff.tsx +12 -17
  361. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +13 -21
  362. package/src/chat/ui/diff/use-review-stack.ts +4 -4
  363. package/src/chat/ui/diff-theme.ts +3 -3
  364. package/src/chat/ui/file-links.ts +2 -2
  365. package/src/chat/ui/intelligent-ui/UiReply.tsx +2 -0
  366. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +3 -3
  367. package/src/chat/ui/intelligent-ui/renderers/content.tsx +5 -6
  368. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +2 -1
  369. package/src/chat/ui/intelligent-ui/renderers/map.tsx +2 -1
  370. package/src/chat/ui/intelligent-ui/use-truncated.ts +16 -19
  371. package/src/chat/ui/intelligent-ui/use-ui-links.ts +3 -3
  372. package/src/chat/ui/rows/ForksRow.tsx +3 -5
  373. package/src/chat/ui/rows/GeneratedImageRow.tsx +2 -1
  374. package/src/chat/ui/rows/MessageRows.tsx +12 -21
  375. package/src/chat/ui/rows/SubagentRow.tsx +3 -3
  376. package/src/chat/ui/rows/TaskRow.tsx +1 -1
  377. package/src/chat/ui/rows/WorkRows.tsx +20 -7
  378. package/src/chat/ui/useToggleSet.ts +15 -18
  379. package/src/chat/ui/visual-theme.ts +2 -3
  380. package/src/host-hooks.ts +113 -0
  381. package/src/lazy.tsx +2 -0
  382. package/src/locales/en/agent-chat.ts +135 -108
  383. package/src/locales/en/agent-prompts.ts +7 -7
  384. package/src/locales/en/agent-providers.ts +35 -34
  385. package/src/locales/en/agent-usage.ts +26 -20
  386. package/src/locales/nl/agent-chat.ts +127 -100
  387. package/src/locales/nl/agent-prompts.ts +5 -5
  388. package/src/locales/nl/agent-providers.ts +33 -32
  389. package/src/locales/nl/agent-usage.ts +25 -20
  390. package/src/prompts/ui/ApprovalBody.tsx +3 -1
  391. package/src/providers/CliDetail.tsx +2 -1
  392. package/src/providers/InstallPage.tsx +2 -4
  393. package/src/providers/ProviderList.tsx +12 -2
  394. package/src/providers/ProvidersPane.tsx +45 -15
  395. package/src/providers/pane-target.ts +20 -4
  396. package/src/state/chats.ts +7 -2
  397. package/src/usage/LimitsList.tsx +12 -13
  398. package/src/usage/UsageBreakdown.tsx +17 -6
  399. package/src/usage/UsageLimits.tsx +2 -1
  400. package/src/usage/UsagePage.tsx +3 -3
  401. package/src/usage/index.ts +1 -1
  402. package/src/usage/limit-groups.ts +12 -0
  403. package/src/usage/limits.ts +2 -2
  404. package/src/usage/money.ts +1 -2
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
1
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { Bot, LoaderCircle } from 'lucide-react';
4
4
  import { deriveTimelineRows } from '../logic/timeline';
@@ -34,8 +34,8 @@ export function SubagentTimeline({ chatId, toolUseId }: { chatId: string; toolUs
34
34
  const scope = useChatScope();
35
35
  const { commandLooks } = useChatHost();
36
36
  const connected = useSyncExternalStore(
37
- useCallback((changed) => scope.transport.subscribeStatus(changed), [scope]),
38
- useCallback(() => scope.transport.status === 'open', [scope]),
37
+ (changed) => scope.transport.subscribeStatus(changed),
38
+ () => scope.transport.status === 'open',
39
39
  () => false
40
40
  );
41
41
  const { trail, show } = useSubagentTrail(chatId);
@@ -46,9 +46,9 @@ export function SubagentTimeline({ chatId, toolUseId }: { chatId: string; toolUs
46
46
  const threadRef = useRef<HTMLDivElement>(null);
47
47
  const [target, setTarget] = useState<TimelineTarget>(EMPTY_TARGET);
48
48
  const followRef = useRef(true);
49
- const stopFollowing = useCallback(() => {
49
+ const stopFollowing = () => {
50
50
  followRef.current = false;
51
- }, []);
51
+ };
52
52
  const groups = useToggleSet(stopFollowing);
53
53
  const subagents = useToggleSet(stopFollowing);
54
54
  // The row being read when an earlier page was asked for, so it stays where it was once that page is in.
@@ -72,17 +72,13 @@ export function SubagentTimeline({ chatId, toolUseId }: { chatId: string; toolUs
72
72
  }
73
73
  }, [state.unsupported, scope]);
74
74
 
75
- const rows = useMemo(
76
- () =>
77
- deriveTimelineRows(state.items, {
78
- expandedGroups: groups.ids,
79
- expandedTurns: NO_TURNS,
80
- expandedSubagents: subagents.ids,
81
- activeTurnId: null,
82
- commandLooks
83
- }),
84
- [state.items, groups.ids, subagents.ids, commandLooks]
85
- );
75
+ const rows = deriveTimelineRows(state.items, {
76
+ expandedGroups: groups.ids,
77
+ expandedTurns: NO_TURNS,
78
+ expandedSubagents: subagents.ids,
79
+ activeTurnId: null,
80
+ commandLooks
81
+ });
86
82
 
87
83
  useLayoutEffect(() => {
88
84
  const element = scrollRef.current;
@@ -79,6 +79,7 @@ import { FileLinkContext } from './file-links';
79
79
  import { AgentIcon } from '../../agents/AgentIcon';
80
80
  import { useModelName } from '../../agents/model-name';
81
81
  import { ContextMenu, EmptyState, ErrorBoundary, Icon, SectionLabel } from '@adecore/ui';
82
+ import { useHostContextSources, useHostHidesFolder, useHostThreadCards, useHostTimelineFind, useHostWelcome } from '../../host-hooks';
82
83
 
83
84
  const ESTIMATED_ROW_PX = 56;
84
85
  // How long a jump waits for a thread that is still loading before it calls the item missing.
@@ -95,12 +96,11 @@ function showRowAtTop(virtualizer: Virtualizer<HTMLDivElement, Element>, followR
95
96
  * and the folder it works in, so a first question can lean on all three.
96
97
  */
97
98
  function EmptyThread({ chatId }: { chatId: string }) {
98
- const host = useChatHost();
99
99
  const { t } = useTranslation('agent-chat');
100
100
  const info = useChatRow(chatId, (row) => row?.info ?? null);
101
101
  const model = useModelName(info?.provider, info?.selection.model ?? '');
102
- const sources = host.useContextSources(chatId);
103
- const hidesFolder = host.useHidesFolder(chatId);
102
+ const sources = useHostContextSources(chatId);
103
+ const hidesFolder = useHostHidesFolder(chatId);
104
104
  return (
105
105
  <div className="flex grow items-center justify-center">
106
106
  <div className="flex max-w-sm flex-col items-center gap-3 px-6 py-8 text-center">
@@ -262,7 +262,7 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
262
262
  }
263
263
  );
264
264
  }, [order, items, groups.ids, turns.ids, subagents.ids, activeTurnId, forkedTurns, host.commandLooks]);
265
- const cards = host.useThreadCards(scopeId, chatId);
265
+ const cards = useHostThreadCards(scopeId, chatId);
266
266
  const drawsVisuals = host.visuals !== null;
267
267
  // Where the part of the thread this client holds begins, while there is a page before it.
268
268
  const heldFrom = cursor === null ? null : (items?.[order?.[0] ?? '']?.createdAt ?? null);
@@ -270,7 +270,7 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
270
270
  () => withTimedRows(threadRows, timedRowsOf(cards, drawsVisuals ? (visuals ?? []) : null, heldFrom)),
271
271
  [threadRows, cards, drawsVisuals, visuals, heldFrom]
272
272
  );
273
- const welcomes = host.useWelcome(chatId);
273
+ const welcomes = useHostWelcome(chatId);
274
274
 
275
275
  const empty = rows.length === 0;
276
276
  // A chat that took a turn before is one whose thread has not come in yet, not one to greet.
@@ -475,7 +475,7 @@ export function Timeline({ chatId, composer, overlay }: { chatId: string; compos
475
475
  }),
476
476
  [chatId, onMainAgent, rows, items, order, virtualizer, turns.add, groups.add, subagents.add, stopFollowing]
477
477
  );
478
- const chatFind = host.useTimelineFind(findOptions);
478
+ const chatFind = useHostTimelineFind(findOptions);
479
479
  const searching = chatFind.open;
480
480
  // A find covers the whole thread, so every page before this one comes in while it is open.
481
481
  useEffect(() => {
@@ -1,4 +1,3 @@
1
- import { useMemo } from 'react';
2
1
  import { LazyBoundary } from '@adecore/ui';
3
2
  import { useTranslation } from 'react-i18next';
4
3
  import { lazyNamed } from '../../lazy';
@@ -20,11 +19,11 @@ export default function UnifiedDiff(props: UnifiedDiffProps) {
20
19
  const loadEngine = host.code.editor;
21
20
  const oldText = contents?.old ?? '';
22
21
  const newText = contents?.new ?? '';
23
- const sourceAllowed = useMemo(() => diffSourceBudget(oldText, newText) && patchSourceBudget(change.diff), [oldText, newText, change.diff]);
22
+ const sourceAllowed = diffSourceBudget(oldText, newText) && patchSourceBudget(change.diff);
24
23
  const legacyMode = !allowEditor || loadEngine === undefined;
25
24
  const hasHunks = /^@@/m.test(change.diff);
26
- const legacyAllowed = useMemo(() => legacyMode && legacyDiffBudget(oldText, newText, change.diff), [legacyMode, oldText, newText, change.diff]);
27
- const rawAllowed = useMemo(() => hasHunks || legacyDiffBudget('', '', change.diff), [hasHunks, change.diff]);
25
+ const legacyAllowed = legacyMode && legacyDiffBudget(oldText, newText, change.diff);
26
+ const rawAllowed = hasHunks || legacyDiffBudget('', '', change.diff);
28
27
  const omitted = <DiffOmitted path={change.path} />;
29
28
  const fallback = legacyAllowed ? (
30
29
  <DiffFallbackBoundary fallback={omitted} resetKeys={[change.path, change.diff, oldText, newText, diffStyle]}>
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
1
+ import { useEffect, useLayoutEffect, useRef, useState } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import clsx from 'clsx';
4
4
  import { visualThemeFragment, type ChatVisual, type VisualAppearance, type VisualTheme } from '@adecore/agent-contracts/visual';
@@ -44,13 +44,10 @@ export function VisualFrame({ chatId, visual, fill = false, className }: VisualF
44
44
  const controllerRef = useRef<VisualViewportController | null>(null);
45
45
  const [viewportReady, setViewportReady] = useState(false);
46
46
  const [measure, width] = useMeasuredWidth();
47
- const setBox = useCallback(
48
- (node: HTMLDivElement | null) => {
49
- boxRef.current = node;
50
- measure(node);
51
- },
52
- [measure]
53
- );
47
+ const setBox = (node: HTMLDivElement | null) => {
48
+ boxRef.current = node;
49
+ measure(node);
50
+ };
54
51
  const theme = useVisualTheme(boxRef);
55
52
  const widthRef = useRef(width);
56
53
  const [reported, setReported] = useState<number | null>(null);
@@ -1,11 +1,11 @@
1
1
  import type { ThemeRegistration } from 'shiki';
2
- import { useChatHost, type ChatHost } from '../../host';
2
+ import { type ChatHost } from '../../host';
3
+ import { useHostCodeMode, useHostCodeThemes } from '../../host-hooks';
3
4
 
4
5
  /* The Shiki theme code is drawn in under the app's current light or dark. */
5
6
  export function useCodeTheme(): string {
6
- const { code } = useChatHost();
7
- const mode = code.useMode();
8
- const themes = code.useThemes();
7
+ const mode = useHostCodeMode();
8
+ const themes = useHostCodeThemes();
9
9
  return mode === 'dark' ? themes.dark : themes.light;
10
10
  }
11
11
 
@@ -1,7 +1,7 @@
1
1
  import { useMemo } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
- import { useChatHost } from '../../../host';
4
3
  import { useChatScope } from '../../../scope';
4
+ import { useHostComposerPlaceholder } from '../../../host-hooks';
5
5
 
6
6
  /* The empty box names its sigils as key caps, so they read as keys to press rather than as punctuation. */
7
7
  function hintedPlaceholder(lead: string, joiner: string, hints: ReadonlyArray<{ key: string; label: string }>): HTMLElement {
@@ -31,7 +31,7 @@ interface PlaceholderInput {
31
31
  export function useHintedPlaceholder({ chatId, disabled, readOnly, mentionable, hasSkills }: PlaceholderInput): string | HTMLElement {
32
32
  const { t } = useTranslation('agent-chat');
33
33
  const scope = useChatScope();
34
- const lead = useChatHost().useComposerPlaceholder(scope.id, chatId);
34
+ const lead = useHostComposerPlaceholder(scope.id, chatId);
35
35
  return useMemo(() => {
36
36
  if (disabled) {
37
37
  return t('composer.placeholder.disconnected');
@@ -1,25 +1,45 @@
1
+ import { useEffect, useState } from 'react';
1
2
  import type { ChatInfo, ChatItem, ProviderCapabilities } from '@adecore/agent-contracts';
2
- import { useNow } from '@adecore/ui';
3
3
  import { useChatRow } from '../../../state/chats';
4
- import { RESUME_COMPACTION_TOKENS, resumeCompactionOffer } from '../../logic/resume-compaction';
4
+ import { RESUME_COMPACTION_TOKENS, resumeCompactionDueAt, resumeCompactionOffer, watchDueAt, type DueClock } from '../../logic/resume-compaction';
5
5
  import { useResumeCompactionDismissal } from '../../resume-compaction-dismissals';
6
6
 
7
- /* The offer to fold a long context before a chat that sat idle goes on, or null when there is none. */
7
+ const PAGE_CLOCK: DueClock = {
8
+ now: () => Date.now(),
9
+ setTimer: (run, ms) => setTimeout(run, ms),
10
+ clearTimer: (timer) => clearTimeout(timer as ReturnType<typeof setTimeout>),
11
+ onWake: (wake) => {
12
+ if (typeof window === 'undefined') {
13
+ return () => {};
14
+ }
15
+ window.addEventListener('focus', wake);
16
+ document.addEventListener('visibilitychange', wake);
17
+ return () => {
18
+ window.removeEventListener('focus', wake);
19
+ document.removeEventListener('visibilitychange', wake);
20
+ };
21
+ }
22
+ };
23
+
24
+ /* The offer to fold a long context before a chat whose cache went cold goes on, or null when there is none. */
8
25
  export function useCompactionOffer(chatId: string, info: ChatInfo, compaction: ProviderCapabilities['compaction'] | undefined) {
9
26
  const order = useChatRow(chatId, (row) => row?.order);
10
- const items = useChatRow(chatId, (row) => row?.structure);
27
+ const structure = useChatRow(chatId, (row) => row?.structure);
11
28
  const dismissedTurnId = useResumeCompactionDismissal(chatId);
12
- // The cheap half of the offer, so a chat that can never make one keeps no timer.
29
+ const [now, setNow] = useState(Date.now);
30
+ // The cheap half of the offer, so a chat that can never make one walks no thread.
13
31
  const couldOffer = compaction !== undefined && compaction !== 'none' && info.usage.contextTokens >= RESUME_COMPACTION_TOKENS;
14
- const now = useNow(60_000, couldOffer);
15
- if (!couldOffer) {
16
- return null;
17
- }
18
- return resumeCompactionOffer({
19
- info,
20
- compaction,
21
- items: (order ?? []).map((id) => items?.[id]).filter((item): item is ChatItem => item !== undefined),
22
- dismissedTurnId,
23
- now
24
- });
32
+ const items = couldOffer ? (order ?? []).map((id) => structure?.[id]).filter((item): item is ChatItem => item !== undefined) : [];
33
+ const input = { info, compaction, items, dismissedTurnId };
34
+ const dueAt = couldOffer ? resumeCompactionDueAt(input) : null;
35
+
36
+ // A watch for the moment the offer comes due, so a chat that can never make one keeps none.
37
+ useEffect(() => {
38
+ if (dueAt === null) {
39
+ return;
40
+ }
41
+ return watchDueAt(dueAt, setNow, PAGE_CLOCK);
42
+ }, [dueAt]);
43
+
44
+ return dueAt === null ? null : resumeCompactionOffer({ ...input, now });
25
45
  }
@@ -23,6 +23,7 @@ import type { ComposerInputHandle } from '../ComposerInput';
23
23
  import type { ComposerDraft } from './use-composer-draft';
24
24
  import { inCode } from './keys';
25
25
  import { clampMenuIndex, composerMenuAria } from './menu-aria';
26
+ import { useHostReferableChats } from '../../../host-hooks';
26
27
 
27
28
  const SEARCH_DEBOUNCE_MS = 80;
28
29
  // What fits above the composer without turning the picker into a file tree.
@@ -62,7 +63,7 @@ export function useComposerMenus({ chatId, info, capabilities, providerName, dra
62
63
  const scope = useChatScope();
63
64
  const host = useChatHost();
64
65
  const { searchFiles } = host;
65
- const chats = host.useReferableChats();
66
+ const chats = useHostReferableChats();
66
67
  const [menuIndex, setMenuIndex] = useState(0);
67
68
  const [mention, setMention] = useState<MentionQuery | null>(null);
68
69
  const [skillQuery, setSkillQuery] = useState<MentionQuery | null>(null);
@@ -156,10 +157,7 @@ export function useComposerMenus({ chatId, info, capabilities, providerName, dra
156
157
  const mentionMenuOpen = !commandMenuOpen && !skillMenuOpen && mention !== null;
157
158
  // Results belong to the query that asked for them; a closed picker shows none while the next answer is on its way.
158
159
  const files = mention === null ? [] : searched.files;
159
- const chatMatches = useMemo(
160
- () => (mention === null ? [] : chatSuggestions(chats, mention.query, [chatId, ...draft.chats], CHAT_RESULTS)),
161
- [chatId, chats, draft.chats, mention]
162
- );
160
+ const chatMatches = mention === null ? [] : chatSuggestions(chats, mention.query, [chatId, ...draft.chats], CHAT_RESULTS);
163
161
  const mentionCount = chatMatches.length + files.length;
164
162
  // One menu is open at a time, so they share an id.
165
163
  const listId = useId();
@@ -167,10 +165,7 @@ export function useComposerMenus({ chatId, info, capabilities, providerName, dra
167
165
  const menuCount = commandMenuOpen ? commands.length : skillMenuOpen ? skillMatches.length : mentionCount;
168
166
  // The list can shrink under a stale index; every reader takes the one that still points at an option.
169
167
  const activeIndex = clampMenuIndex(menuIndex, menuCount);
170
- const inputAria = useMemo(
171
- () => composerMenuAria({ label: t('composer.inputLabel'), listId, open: menuOpen, activeIndex, count: menuCount }),
172
- [t, listId, menuOpen, activeIndex, menuCount]
173
- );
168
+ const inputAria = composerMenuAria({ label: t('composer.inputLabel'), listId, open: menuOpen, activeIndex, count: menuCount });
174
169
 
175
170
  /* Which picker the caret opens: `@` for a file, `$` for a skill, or neither. In code a sigil is just text. */
176
171
  const trackTriggers = (value: string, selection: TextRange, state: EditorState): void => {
@@ -19,6 +19,7 @@ import type { ComposerDraft } from './use-composer-draft';
19
19
  import type { ComposerMenus } from './use-composer-menus';
20
20
  import type { PromptHistory } from './use-prompt-history';
21
21
  import type { ComposerPromptHold } from './use-prompt-hold';
22
+ import { useHostReferableChats } from '../../../host-hooks';
22
23
 
23
24
  interface SubmitInput {
24
25
  chatId: string;
@@ -58,7 +59,7 @@ export function useComposerSubmit({
58
59
  const host = useChatHost();
59
60
  const scope = useChatScope();
60
61
  const { draft, setDraft } = box;
61
- const referable = host.useReferableChats();
62
+ const referable = useHostReferableChats();
62
63
 
63
64
  /* Opens the host's new chat once the host took the message; a host that answers nothing took it. */
64
65
  const afterSend = (sent: void | Promise<boolean>, andNew: boolean): void => {
@@ -0,0 +1,56 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import type { ChatInfo, ModelInfo } from '@adecore/agent-contracts';
4
+ import { useChatScope } from '../../../scope';
5
+ import { choiceWord } from '../../../agents/option-words';
6
+ import { registerEffortStepper } from '../../composer-effort';
7
+ import { EFFORT_OPTION, stepEffort, type EffortStep } from '../../logic/effort';
8
+ import type { ChatControls } from './use-chat-controls';
9
+ import type { ComposerNotice } from './use-notice';
10
+
11
+ interface EffortStepperInput {
12
+ chatId: string;
13
+ info: ChatInfo;
14
+ model: ModelInfo | undefined;
15
+ writable: boolean;
16
+ controls: ChatControls;
17
+ setNotice: ComposerNotice['setNotice'];
18
+ }
19
+
20
+ /* Lets a command of the host step this chat's reasoning effort, as picking it in the pill's menu does, and says where it landed. */
21
+ export function useEffortStepper({ chatId, info, model, writable, controls, setNotice }: EffortStepperInput): void {
22
+ const { t } = useTranslation('agent-chat');
23
+ const scope = useChatScope();
24
+
25
+ const step = (direction: EffortStep): void => {
26
+ // The pill is disabled then too, and a second configuration would be dropped anyway.
27
+ if (!writable || controls.configurationPending) {
28
+ return;
29
+ }
30
+ const name = model?.name ?? info.selection.model;
31
+ const result = stepEffort(model, info.selection, direction);
32
+ if (result.kind === 'none') {
33
+ setNotice(t('composer.notice.noEffort', { model: name }), 'info');
34
+ return;
35
+ }
36
+ if (result.kind === 'edge') {
37
+ setNotice(
38
+ t(direction === 'up' ? 'composer.notice.effortHighest' : 'composer.notice.effortLowest', {
39
+ effort: choiceWord(EFFORT_OPTION, result.choice),
40
+ model: name
41
+ }),
42
+ 'info'
43
+ );
44
+ return;
45
+ }
46
+ controls.configure({ selection: result.selection });
47
+ setNotice(t('composer.notice.effort', { effort: choiceWord(EFFORT_OPTION, result.choice) }), 'info');
48
+ };
49
+
50
+ // A command means the chat as it stands when it is given, so the registration reads the latest step.
51
+ const stepRef = useRef(step);
52
+ useEffect(() => {
53
+ stepRef.current = step;
54
+ });
55
+ useEffect(() => registerEffortStepper(scope.keyOf(chatId), (direction) => stepRef.current(direction)), [scope, chatId]);
56
+ }
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect, useState } from 'react';
1
+ import { useEffect, useState } from 'react';
2
2
 
3
3
  const NOTICE_MS = 4000;
4
4
 
@@ -24,7 +24,7 @@ export function useNotice(): ComposerNotice {
24
24
  return () => clearTimeout(timer);
25
25
  }, [shown]);
26
26
 
27
- const setNotice = useCallback((notice: string | null, tone: NoticeTone = 'error'): void => setShown(notice ? { text: notice, tone } : null), []);
27
+ const setNotice = (notice: string | null, tone: NoticeTone = 'error'): void => setShown(notice ? { text: notice, tone } : null);
28
28
 
29
29
  return {
30
30
  notice: shown?.text ?? null,
@@ -1,16 +1,16 @@
1
1
  import { useReviewSettled } from './review-settle';
2
- import { useEffect, useMemo, useState, type ReactNode } from 'react';
2
+ import { useEffect, useState, type ReactNode } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  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 { useChatHost } from '../../../host';
9
8
  import type { FileChange } from '../../logic/tools';
10
9
  import { useDiffSourceTokens } from '../use-diff-source-tokens';
11
10
  import { diffSourceBudget } from './diff-source-budget';
12
11
  import { DiffOmitted } from './DiffOmitted';
13
12
  import { canRenderEditorDiff, EDITOR_DIFF_LIMITS, EDITOR_DIFF_WORK } from './editor-edit-model';
13
+ import { useHostCodeMode, useHostCodeThemes } from '../../../host-hooks';
14
14
 
15
15
  type EngineLoader = () => Promise<EditorEngine>;
16
16
 
@@ -34,11 +34,10 @@ function ReadyDiff({
34
34
  layout: 'unified' | 'split';
35
35
  maxHeight: number;
36
36
  }) {
37
- const host = useChatHost();
38
37
  useReviewSettled();
39
38
  const { t } = useTranslation('agent-chat');
40
- const mode = host.code.useMode();
41
- const theme = host.code.useThemes()[mode];
39
+ const mode = useHostCodeMode();
40
+ const theme = useHostCodeThemes()[mode];
42
41
  const language = getFiletypeFromFileName(path);
43
42
  const tokens = useDiffSourceTokens(document, language, theme);
44
43
  return (
@@ -78,7 +77,7 @@ export function EditorDocumentDiff({
78
77
  maxHeight?: number;
79
78
  }) {
80
79
  const { t } = useTranslation('agent-chat');
81
- const withinLimits = useMemo(() => canRenderEditorDiff(document), [document]);
80
+ const withinLimits = canRenderEditorDiff(document);
82
81
  const [loaded, setLoaded] = useState<{ load: EngineLoader; engine: EditorEngine | null }>();
83
82
  useEffect(() => {
84
83
  if (!withinLimits || engines.has(loadEngine)) {
@@ -132,13 +131,10 @@ export default function EditorEditDiff({
132
131
  fallback: ReactNode;
133
132
  maxHeight?: number;
134
133
  }) {
135
- const document = useMemo(
136
- () =>
137
- sourceChecked || diffSourceBudget(change.before, change.after)
138
- ? diffDocument(patchOf(change.before, change.after, 3, { maxWork: EDITOR_DIFF_WORK }), { old: change.before, new: change.after })
139
- : undefined,
140
- [change.before, change.after, sourceChecked]
141
- );
134
+ const document =
135
+ sourceChecked || diffSourceBudget(change.before, change.after)
136
+ ? diffDocument(patchOf(change.before, change.after, 3, { maxWork: EDITOR_DIFF_WORK }), { old: change.before, new: change.after })
137
+ : undefined;
142
138
  if (document === undefined) {
143
139
  return <DiffOmitted path={change.path} />;
144
140
  }
@@ -1,29 +1,24 @@
1
1
  import { LegacyDiffPool } from './LegacyDiffPool';
2
2
  import { useReviewSettled } from './review-settle';
3
- import { useMemo } from 'react';
4
3
  import { MultiFileDiff } from '@pierre/diffs/react';
5
- import { useChatHost } from '../../../host';
6
4
  import { useDiffTheme } from '../diff-theme';
7
5
  import type { FileChange } from '../../logic/tools';
6
+ import { useHostCodeMode } from '../../../host-hooks';
8
7
 
9
8
  export default function LegacyEditDiff({ change, maxHeight }: { change: FileChange; maxHeight?: number }) {
10
- const host = useChatHost();
11
9
  useReviewSettled();
12
- const resolved = host.code.useMode();
10
+ const resolved = useHostCodeMode();
13
11
  const theme = useDiffTheme();
14
- const oldFile = useMemo(() => ({ name: change.path, contents: change.before }), [change.path, change.before]);
15
- const newFile = useMemo(() => ({ name: change.path, contents: change.after }), [change.path, change.after]);
16
- const options = useMemo(
17
- () => ({
18
- diffStyle: 'unified' as const,
19
- theme,
20
- themeType: resolved,
21
- disableFileHeader: true,
22
- overflow: 'wrap' as const,
23
- hunkSeparators: 'simple' as const
24
- }),
25
- [theme, resolved]
26
- );
12
+ const oldFile = { name: change.path, contents: change.before };
13
+ const newFile = { name: change.path, contents: change.after };
14
+ const options = {
15
+ diffStyle: 'unified' as const,
16
+ theme,
17
+ themeType: resolved,
18
+ disableFileHeader: true,
19
+ overflow: 'wrap' as const,
20
+ hunkSeparators: 'simple' as const
21
+ };
27
22
  return (
28
23
  <div className="chat-diff select-text" style={maxHeight === undefined ? undefined : { maxHeight, overflow: 'auto' }}>
29
24
  <LegacyDiffPool>
@@ -1,13 +1,12 @@
1
1
  import { LegacyDiffPool } from './LegacyDiffPool';
2
2
  import { useReviewSettled } from './review-settle';
3
- import { useMemo } from 'react';
4
3
  import clsx from 'clsx';
5
4
  import { FileDiff, PatchDiff } from '@pierre/diffs/react';
6
5
  import type { ChatFileChange } from '@adecore/agent-contracts';
7
- import { useChatHost } from '../../../host';
8
6
  import { useDiffTheme } from '../diff-theme';
9
7
  import { fullFileDiff } from '../full-diff';
10
8
  import type { UnifiedDiffProps } from './diff-props';
9
+ import { useHostCodeMode } from '../../../host-hooks';
11
10
 
12
11
  /*
13
12
  * A patch the CLI reported itself. The renderer needs a file header, which a CLI that only sends hunks
@@ -45,30 +44,23 @@ function lineClass(line: string): string {
45
44
  }
46
45
 
47
46
  export default function LegacyUnifiedDiff({ change, overflow = 'wrap', diffStyle = 'unified', fill = false, contents, maxHeight }: UnifiedDiffProps) {
48
- const host = useChatHost();
49
47
  useReviewSettled();
50
- const resolved = host.code.useMode();
48
+ const resolved = useHostCodeMode();
51
49
  const theme = useDiffTheme();
52
- const patch = useMemo(() => asPatch(change), [change]);
50
+ const patch = asPatch(change);
53
51
  const oldText = contents?.old;
54
52
  const newText = contents?.new;
55
- const full = useMemo(
56
- () => (patch === null || oldText === undefined || newText === undefined ? null : fullFileDiff(patch, change.path, { old: oldText, new: newText })),
57
- [patch, change.path, oldText, newText]
58
- );
53
+ const full = patch === null || oldText === undefined || newText === undefined ? null : fullFileDiff(patch, change.path, { old: oldText, new: newText });
59
54
  const unfolds = full !== null;
60
- const options = useMemo(
61
- () => ({
62
- theme,
63
- themeType: resolved,
64
- disableFileHeader: true,
65
- diffStyle,
66
- overflow,
67
- hunkSeparators: unfolds ? ('line-info' as const) : ('simple' as const),
68
- unsafeCSS: fill ? FILL_CSS : undefined
69
- }),
70
- [diffStyle, overflow, theme, resolved, fill, unfolds]
71
- );
55
+ const options = {
56
+ theme,
57
+ themeType: resolved,
58
+ disableFileHeader: true,
59
+ diffStyle,
60
+ overflow,
61
+ hunkSeparators: unfolds ? ('line-info' as const) : ('simple' as const),
62
+ unsafeCSS: fill ? FILL_CSS : undefined
63
+ };
72
64
  if (patch === null) {
73
65
  return (
74
66
  <pre className="max-h-64 overflow-auto px-3 py-2 font-mono text-code select-text" style={fill ? undefined : { maxHeight }}>
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect, useMemo, useState } from 'react';
1
+ import { useEffect, useState } from 'react';
2
2
  import { planReview, type ReviewEntry, type ReviewSlot } from './review-plan';
3
3
 
4
4
  export interface ReviewStack {
@@ -10,7 +10,7 @@ export interface ReviewStack {
10
10
 
11
11
  export function useReviewStack(entries: readonly ReviewEntry[]): ReviewStack {
12
12
  const [promoted, setPromoted] = useState<string | null>(null);
13
- const slots = useMemo(() => planReview(entries, promoted), [entries, promoted]);
13
+ const slots = planReview(entries, promoted);
14
14
  const selected = [...slots].filter(([, slot]) => slot === 'editor').map(([key]) => key);
15
15
  const [admitted, setAdmitted] = useState<string[]>(() => selected.slice(0, 1));
16
16
  const retained = admitted.filter((key) => selected.includes(key));
@@ -22,9 +22,9 @@ export function useReviewStack(entries: readonly ReviewEntry[]): ReviewStack {
22
22
  if ([...settled].some((key) => !selected.includes(key))) {
23
23
  setSettled(new Set([...settled].filter((key) => selected.includes(key))));
24
24
  }
25
- const settle = useCallback((key: string) => {
25
+ const settle = (key: string) => {
26
26
  setSettled((previous) => (previous.has(key) ? previous : new Set([...previous, key])));
27
- }, []);
27
+ };
28
28
  const allSettled = live.every((key) => settled.has(key));
29
29
  const next = selected.find((key) => !live.includes(key));
30
30
  useEffect(() => {
@@ -1,6 +1,6 @@
1
- import { useMemo } from 'react';
2
1
  import { registerCustomTheme, type ThemesType } from '@pierre/diffs';
3
2
  import { useChatHost, type ChatHost } from '../../host';
3
+ import { useHostCodeThemes } from '../../host-hooks';
4
4
 
5
5
  // The library's registry is global and refuses a name twice, so what is registered is kept by name.
6
6
  const registered = new Set<string>();
@@ -23,6 +23,6 @@ function registerThemes(custom: ChatHost['code']['custom']): void {
23
23
  export function useDiffTheme(): ThemesType {
24
24
  const { code } = useChatHost();
25
25
  registerThemes(code.custom);
26
- const { light, dark } = code.useThemes();
27
- return useMemo(() => ({ light, dark }), [light, dark]);
26
+ const { light, dark } = useHostCodeThemes();
27
+ return { light, dark };
28
28
  }
@@ -1,4 +1,4 @@
1
- import { createContext, useContext, useMemo } from 'react';
1
+ import { createContext, useContext } from 'react';
2
2
  import { useChatHost, type FileRef } from '../../host';
3
3
  import { ChatScopeContext } from '../../scope';
4
4
 
@@ -22,7 +22,7 @@ export function useFileLinkTarget(text: string): FileRef | null {
22
22
  const cwd = useFileLinkCwd();
23
23
  const scopeId = useFileLinkScopeId();
24
24
  const { fileLinks } = useChatHost();
25
- return useMemo(() => fileLinks?.target(text, cwd, scopeId) ?? null, [fileLinks, text, cwd, scopeId]);
25
+ return fileLinks?.target(text, cwd, scopeId) ?? null;
26
26
  }
27
27
 
28
28
  /* Follows a reference the way the app opens a file or a folder. */
@@ -399,6 +399,8 @@ function UiBlockBody({
399
399
  /* Set for a finished reply the machine keeps no readings for, which nothing will ever look up. */
400
400
  unrecorded: boolean;
401
401
  }): ReactNode {
402
+ // The block's state changes in place and only its revision says so; compiled, the evaluation of the first revision would stay.
403
+ 'use no memo';
402
404
  const host = useChatHost();
403
405
  const { t } = useTranslation('agent-chat');
404
406
  const key = JSON.stringify([context.scopeId, context.chatId, context.itemId, block.id]);
@@ -1,4 +1,4 @@
1
- import { useMemo, useState } from 'react';
1
+ import { useState } from 'react';
2
2
  import type { TFunction } from 'i18next';
3
3
  import clsx from 'clsx';
4
4
  import { useTranslation } from 'react-i18next';
@@ -439,7 +439,7 @@ function DrawnChart({
439
439
  const [measure, width] = useMeasuredWidth();
440
440
  const character = useChartCharacter();
441
441
  const [measureRows, rowsCut] = useTruncated<HTMLDivElement>(true);
442
- const values = useMemo(() => uiChartValues(data), [data]);
442
+ const values = uiChartValues(data);
443
443
  const { kind } = chart;
444
444
  return (
445
445
  <figure className="chat-ui-flat flex flex-col gap-2.5 rounded-xl bg-ui-tile p-3.5">
@@ -473,7 +473,7 @@ function DrawnChart({
473
473
  */
474
474
  export function ChartRenderer({ node, context }: UiRendererProps<UiProps<'Chart'>>) {
475
475
  const { kind, data, unit } = node.props;
476
- const chart = useMemo(() => (node.complete ? uiChartData(data, kind) : null), [node.complete, data, kind]);
476
+ const chart = node.complete ? uiChartData(data, kind) : null;
477
477
  if (!node.complete) {
478
478
  return <div aria-hidden className="rounded-xl bg-ui-tile" style={{ height: chartHeight(kind, data.length) }} />;
479
479
  }