@adecore/agents-react 0.31.12 → 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 (402) 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 +1945 -164
  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 +34 -0
  241. package/dist/locales/en/agent-chat.js +747 -713
  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 +34 -0
  248. package/dist/locales/nl/agent-chat.js +747 -713
  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 -27
  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.js +404 -192
  291. package/dist/state/index.js +1 -1
  292. package/dist/state/provider-accounts.js +70 -47
  293. package/dist/state/providers.js +45 -16
  294. package/dist/state/usage.js +154 -77
  295. package/dist/storage.js +69 -68
  296. package/dist/testing/index.js +1 -1
  297. package/dist/testing/prompts.js +105 -67
  298. package/dist/transport.js +10 -10
  299. package/dist/ui/PlainTextarea.js +27 -3
  300. package/dist/usage/LimitsList.js +646 -40
  301. package/dist/usage/UsageBreakdown.js +879 -38
  302. package/dist/usage/UsageChart.js +311 -32
  303. package/dist/usage/UsageDialog.js +46 -4
  304. package/dist/usage/UsageLimits.js +467 -34
  305. package/dist/usage/UsageLimitsCard.js +95 -11
  306. package/dist/usage/UsagePage.js +817 -96
  307. package/dist/usage/UsagePane.js +164 -10
  308. package/dist/usage/UsageSummary.js +151 -6
  309. package/dist/usage/UsageTiles.js +164 -14
  310. package/dist/usage/format.js +32 -32
  311. package/dist/usage/index.d.ts +1 -1
  312. package/dist/usage/index.js +2 -1
  313. package/dist/usage/limit-groups.d.ts +2 -0
  314. package/dist/usage/limit-groups.js +77 -59
  315. package/dist/usage/limits.js +78 -43
  316. package/dist/usage/money.js +20 -4
  317. package/dist/usage/summary.js +85 -64
  318. package/package.json +8 -8
  319. package/src/agents/ModelOptionControl.tsx +3 -1
  320. package/src/agents/option-words.ts +27 -0
  321. package/src/chat/account-choice.ts +41 -1
  322. package/src/chat/composer-effort.ts +32 -0
  323. package/src/chat/forks.ts +4 -5
  324. package/src/chat/index.ts +3 -1
  325. package/src/chat/logic/effort.ts +32 -0
  326. package/src/chat/logic/picker-entries.ts +45 -10
  327. package/src/chat/logic/resume-compaction.ts +95 -17
  328. package/src/chat/logic/timeline.ts +11 -3
  329. package/src/chat/preferences.ts +35 -0
  330. package/src/chat/subagent-view.ts +2 -3
  331. package/src/chat/ui/AnsiOutput.tsx +1 -2
  332. package/src/chat/ui/BookmarkSubmenu.tsx +3 -3
  333. package/src/chat/ui/ChatActivity.tsx +14 -22
  334. package/src/chat/ui/ChatReferenceChip.tsx +2 -3
  335. package/src/chat/ui/Composer.tsx +15 -7
  336. package/src/chat/ui/EditDiff.tsx +2 -3
  337. package/src/chat/ui/LimitState.tsx +2 -2
  338. package/src/chat/ui/Markdown.tsx +10 -8
  339. package/src/chat/ui/MermaidBlock.tsx +2 -3
  340. package/src/chat/ui/Pickers.tsx +49 -29
  341. package/src/chat/ui/PromptComposer.tsx +7 -9
  342. package/src/chat/ui/ResumeCompactionDock.tsx +16 -4
  343. package/src/chat/ui/RunSettings.tsx +67 -28
  344. package/src/chat/ui/Scrubber.tsx +3 -3
  345. package/src/chat/ui/ShellCommand.tsx +2 -0
  346. package/src/chat/ui/SubagentInfo.tsx +2 -3
  347. package/src/chat/ui/SubagentTimeline.tsx +12 -16
  348. package/src/chat/ui/Timeline.tsx +6 -6
  349. package/src/chat/ui/UnifiedDiff.tsx +3 -4
  350. package/src/chat/ui/VisualFrame.tsx +5 -8
  351. package/src/chat/ui/code-theme.ts +4 -4
  352. package/src/chat/ui/composer/placeholder.ts +2 -2
  353. package/src/chat/ui/composer/use-compaction-offer.ts +36 -16
  354. package/src/chat/ui/composer/use-composer-menus.ts +4 -9
  355. package/src/chat/ui/composer/use-composer-submit.ts +2 -1
  356. package/src/chat/ui/composer/use-effort-stepper.ts +56 -0
  357. package/src/chat/ui/composer/use-notice.ts +2 -2
  358. package/src/chat/ui/diff/EditorEditDiff.tsx +9 -13
  359. package/src/chat/ui/diff/LegacyEditDiff.tsx +12 -17
  360. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +13 -21
  361. package/src/chat/ui/diff/use-review-stack.ts +4 -4
  362. package/src/chat/ui/diff-theme.ts +3 -3
  363. package/src/chat/ui/file-links.ts +2 -2
  364. package/src/chat/ui/intelligent-ui/UiReply.tsx +2 -0
  365. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +3 -3
  366. package/src/chat/ui/intelligent-ui/renderers/content.tsx +5 -6
  367. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +2 -1
  368. package/src/chat/ui/intelligent-ui/renderers/map.tsx +2 -1
  369. package/src/chat/ui/intelligent-ui/use-truncated.ts +16 -19
  370. package/src/chat/ui/intelligent-ui/use-ui-links.ts +3 -3
  371. package/src/chat/ui/rows/ForksRow.tsx +3 -5
  372. package/src/chat/ui/rows/GeneratedImageRow.tsx +2 -1
  373. package/src/chat/ui/rows/MessageRows.tsx +12 -21
  374. package/src/chat/ui/rows/SubagentRow.tsx +3 -3
  375. package/src/chat/ui/rows/TaskRow.tsx +1 -1
  376. package/src/chat/ui/rows/WorkRows.tsx +10 -5
  377. package/src/chat/ui/useToggleSet.ts +15 -18
  378. package/src/chat/ui/visual-theme.ts +2 -3
  379. package/src/host-hooks.ts +113 -0
  380. package/src/lazy.tsx +2 -0
  381. package/src/locales/en/agent-chat.ts +134 -108
  382. package/src/locales/en/agent-prompts.ts +7 -7
  383. package/src/locales/en/agent-providers.ts +35 -34
  384. package/src/locales/en/agent-usage.ts +26 -20
  385. package/src/locales/nl/agent-chat.ts +126 -100
  386. package/src/locales/nl/agent-prompts.ts +5 -5
  387. package/src/locales/nl/agent-providers.ts +33 -32
  388. package/src/locales/nl/agent-usage.ts +25 -20
  389. package/src/providers/CliDetail.tsx +2 -1
  390. package/src/providers/InstallPage.tsx +2 -4
  391. package/src/providers/ProviderList.tsx +12 -2
  392. package/src/providers/ProvidersPane.tsx +45 -15
  393. package/src/providers/pane-target.ts +20 -4
  394. package/src/state/chats.ts +2 -2
  395. package/src/usage/LimitsList.tsx +12 -13
  396. package/src/usage/UsageBreakdown.tsx +17 -6
  397. package/src/usage/UsageLimits.tsx +2 -1
  398. package/src/usage/UsagePage.tsx +3 -3
  399. package/src/usage/index.ts +1 -1
  400. package/src/usage/limit-groups.ts +12 -0
  401. package/src/usage/limits.ts +2 -2
  402. package/src/usage/money.ts +1 -2
@@ -0,0 +1,32 @@
1
+ import type { EffortStep } from './logic/effort';
2
+
3
+ type Stepper = (step: EffortStep) => void;
4
+
5
+ /*
6
+ * A command outside the composer, such as a menu row or a shortcut of the app, steps the reasoning effort
7
+ * of a chat whose composer is on screen. Keyed with the scope's `keyOf`, like `sendFromComposer`. One chat can
8
+ * have two composers on screen; the one that came last steps, and the one before takes over when it goes.
9
+ */
10
+ const steppers = new Map<string, Stepper[]>();
11
+
12
+ export function registerEffortStepper(key: string, stepper: Stepper): () => void {
13
+ steppers.set(key, [...(steppers.get(key) ?? []), stepper]);
14
+ return () => {
15
+ const left = (steppers.get(key) ?? []).filter((entry) => entry !== stepper);
16
+ if (left.length > 0) {
17
+ steppers.set(key, left);
18
+ } else {
19
+ steppers.delete(key);
20
+ }
21
+ };
22
+ }
23
+
24
+ /* False when that chat has no composer on screen. At either end of the model's efforts the composer says so and nothing changes. */
25
+ export function stepEffortFromComposer(key: string, step: EffortStep): boolean {
26
+ const stepper = steppers.get(key)?.at(-1);
27
+ if (stepper === undefined) {
28
+ return false;
29
+ }
30
+ stepper(step);
31
+ return true;
32
+ }
package/src/chat/forks.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { useMemo } from 'react';
2
1
  import type { ChatInfo } from '@adecore/agent-contracts';
3
2
  import { forkIdsAfter, forkedTurnIds } from './logic/fork';
4
3
  import { useChatScope } from '../scope';
@@ -40,15 +39,15 @@ function splitIds(joined: string): string[] {
40
39
  /* The turns of this chat a fork went on after, for the thread to put a line under. */
41
40
  export function useForkedTurns(chatId: string): ReadonlySet<string> {
42
41
  const { owns } = useChatScope();
43
- const select = useMemo(() => joinedForks(owns, (infos) => forkedTurnIds(infos, chatId)), [owns, chatId]);
42
+ const select = joinedForks(owns, (infos) => forkedTurnIds(infos, chatId));
44
43
  const joined = useChats(select);
45
- return useMemo(() => new Set(splitIds(joined)), [joined]);
44
+ return new Set(splitIds(joined));
46
45
  }
47
46
 
48
47
  /* The forks that went on after this turn of this chat. */
49
48
  export function useForkIdsAfter(chatId: string, turnId: string): string[] {
50
49
  const { owns } = useChatScope();
51
- const select = useMemo(() => joinedForks(owns, (infos) => forkIdsAfter(infos, chatId, turnId)), [owns, chatId, turnId]);
50
+ const select = joinedForks(owns, (infos) => forkIdsAfter(infos, chatId, turnId));
52
51
  const joined = useChats(select);
53
- return useMemo(() => splitIds(joined), [joined]);
52
+ return splitIds(joined);
54
53
  }
package/src/chat/index.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  // Public API: exports.test.ts snapshots this list, so a name added or dropped here is an API change.
2
- export { type AccountChoice, useAccountChoice } from './account-choice';
2
+ export { type AccountChoice, useAccountChoice, type NewAccountChoice, useNewAccountChoice } from './account-choice';
3
3
  export { transportActions, actionsOf, useChatActions } from './actions';
4
4
  export { type ChatSendExtras, ChatClient } from './chat-client';
5
5
  export type { ProjectChat } from './chat-references';
6
6
  export { sendFromComposer, type ComposerSend } from './composer-send';
7
+ export { stepEffortFromComposer } from './composer-effort';
8
+ export type { EffortStep } from './logic/effort';
7
9
  export { EMPTY_DRAFT, useDrafts, readDraft, writeDraft, offerDraft, type ChatDraft } from './drafts';
8
10
  export { stripAnsi } from './logic/ansi';
9
11
  export {
@@ -0,0 +1,32 @@
1
+ import type { ModelInfo, ModelOptionChoice, ModelSelection } from '@adecore/agent-contracts';
2
+
3
+ /* The option a catalog names the reasoning effort by; its choices run from the least to the most. */
4
+ export const EFFORT_OPTION = 'effort';
5
+
6
+ /* `up` is one choice more effort, `down` one less. */
7
+ export type EffortStep = 'up' | 'down';
8
+
9
+ export type EffortStepResult =
10
+ | { kind: 'stepped'; selection: ModelSelection; choice: ModelOptionChoice }
11
+ /* Already at the end of the model's choices that way, which `choice` names. */
12
+ | { kind: 'edge'; choice: ModelOptionChoice }
13
+ /* The model has no effort to step, or the catalog does not know the model. */
14
+ | { kind: 'none' };
15
+
16
+ /* One step of effort within what the model offers. A value the model no longer offers counts as its default. */
17
+ export function stepEffort(model: ModelInfo | undefined, selection: ModelSelection, step: EffortStep): EffortStepResult {
18
+ const option = model?.options.find((candidate) => candidate.id === EFFORT_OPTION);
19
+ if (option === undefined || option.type !== 'select') {
20
+ return { kind: 'none' };
21
+ }
22
+ const chosen = selection.options[EFFORT_OPTION];
23
+ const index = option.choices.findIndex((choice) => choice.id === chosen);
24
+ const from = index === -1 ? option.choices.findIndex((choice) => choice.id === option.defaultChoice) : index;
25
+ const current = Math.max(0, from);
26
+ const next = current + (step === 'up' ? 1 : -1);
27
+ const choice = option.choices[next];
28
+ if (choice === undefined) {
29
+ return { kind: 'edge', choice: option.choices[current]! };
30
+ }
31
+ return { kind: 'stepped', selection: { ...selection, options: { ...selection.options, [EFFORT_OPTION]: choice.id } }, choice };
32
+ }
@@ -17,6 +17,11 @@ export interface PickerCurrent {
17
17
  model: string;
18
18
  }
19
19
 
20
+ /* The models a person starred, per CLI. */
21
+ export type FavoritesOf = (kind: AgentKind) => ReadonlySet<string>;
22
+
23
+ const NO_FAVORITES: FavoritesOf = () => new Set();
24
+
20
25
  function matches(model: ModelInfo, query: string): boolean {
21
26
  return model.name.toLowerCase().includes(query) || model.slug.toLowerCase().includes(query) || (model.vendor?.toLowerCase().includes(query) ?? false);
22
27
  }
@@ -40,12 +45,31 @@ export function modelsByVendor(models: ModelInfo[]): ModelInfo[] {
40
45
  });
41
46
  }
42
47
 
43
- /* The models a long list shows before it is opened: the default and the one that is picked. Null when the list is short enough to show whole. */
44
- export function shortlistOf(kind: AgentKind, models: ModelInfo[], current: PickerCurrent | null): ModelInfo[] | null {
48
+ /* The starred models first, in the order they came, then the rest by maker. */
49
+ export function favoritesFirst(models: ModelInfo[], favorites: ReadonlySet<string>): ModelInfo[] {
50
+ if (favorites.size === 0) {
51
+ return modelsByVendor(models);
52
+ }
53
+ return [...models.filter((model) => favorites.has(model.slug)), ...modelsByVendor(models.filter((model) => !favorites.has(model.slug)))];
54
+ }
55
+
56
+ /*
57
+ * The models a long list shows before it is opened: the starred ones, the default and the one that is picked.
58
+ * Null when the list is short enough to show whole.
59
+ */
60
+ export function shortlistOf(
61
+ kind: AgentKind,
62
+ models: ModelInfo[],
63
+ current: PickerCurrent | null,
64
+ favorites: ReadonlySet<string> = new Set()
65
+ ): ModelInfo[] | null {
45
66
  if (models.length <= COMPACT_MODEL_LIMIT) {
46
67
  return null;
47
68
  }
48
- return modelsByVendor(models.filter((model) => model.isDefault || (current?.provider === kind && current.model === model.slug)));
69
+ return favoritesFirst(
70
+ models.filter((model) => model.isDefault || favorites.has(model.slug) || (current?.provider === kind && current.model === model.slug)),
71
+ favorites
72
+ );
49
73
  }
50
74
 
51
75
  /* Whether the providers offer enough models together that a search is quicker than reading the list. */
@@ -53,11 +77,21 @@ export function wantsModelSearch(providers: readonly ProviderInfo[]): boolean {
53
77
  return providers.reduce((count, entry) => count + entry.models.length, 0) > COMPACT_MODEL_LIMIT;
54
78
  }
55
79
 
56
- /* Every model of every provider that a search finds, legacy ones included, by maker; a provider with none is left out. */
57
- export function searchModels(providers: readonly ProviderInfo[], query: string): Array<{ entry: ProviderInfo; models: ModelInfo[] }> {
80
+ /* Every model of every provider that a search finds, legacy ones included, starred first and then by maker; a provider with none is left out. */
81
+ export function searchModels(
82
+ providers: readonly ProviderInfo[],
83
+ query: string,
84
+ favoritesOf: FavoritesOf = NO_FAVORITES
85
+ ): Array<{ entry: ProviderInfo; models: ModelInfo[] }> {
58
86
  const needle = query.trim().toLowerCase();
59
87
  return providers
60
- .map((entry) => ({ entry, models: modelsByVendor(entry.models.filter((model) => matches(model, needle))) }))
88
+ .map((entry) => ({
89
+ entry,
90
+ models: favoritesFirst(
91
+ entry.models.filter((model) => matches(model, needle)),
92
+ favoritesOf(entry.kind)
93
+ )
94
+ }))
61
95
  .filter((group) => group.models.length > 0);
62
96
  }
63
97
 
@@ -67,17 +101,18 @@ const NONE: ReadonlySet<AgentKind> = new Set();
67
101
  * The rows of the model list. A model an old CLI cannot run is a row like any other, so a search for
68
102
  * only that model still finds the row that explains why it cannot be picked. A search looks through
69
103
  * every model of every provider; without one, a provider with many models shows a short list and a row
70
- * that opens the rest, grouped by maker.
104
+ * that opens the rest, grouped by maker. A provider's starred models come first wherever it lists them.
71
105
  */
72
106
  export function pickerEntriesOf(
73
107
  providers: readonly ProviderInfo[],
74
108
  query: string,
75
109
  legacyOpen: boolean,
76
110
  expanded: ReadonlySet<AgentKind> = NONE,
77
- current: PickerCurrent | null = null
111
+ current: PickerCurrent | null = null,
112
+ favoritesOf: FavoritesOf = NO_FAVORITES
78
113
  ): PickerEntry[] {
79
114
  const rowsOf = (entry: ProviderInfo, models: ModelInfo[]): PickerEntry[] =>
80
- modelsByVendor(models).map((model) => ({ kind: 'model', provider: entry, model }));
115
+ favoritesFirst(models, favoritesOf(entry.kind)).map((model) => ({ kind: 'model', provider: entry, model }));
81
116
  if (query !== '') {
82
117
  return providers.flatMap((entry) =>
83
118
  rowsOf(
@@ -91,7 +126,7 @@ export function pickerEntriesOf(
91
126
  );
92
127
  const live = providers.flatMap((entry): PickerEntry[] => {
93
128
  const models = entry.models.filter((model) => !model.legacy);
94
- const shortlist = shortlistOf(entry.kind, models, current);
129
+ const shortlist = shortlistOf(entry.kind, models, current, favoritesOf(entry.kind));
95
130
  if (shortlist === null) {
96
131
  return rowsOf(entry, models);
97
132
  }
@@ -9,31 +9,39 @@ export const RESUME_COMPACTION_TOKENS = 100_000;
9
9
  /* The longest prompt cache Claude keeps is an hour, so past this the whole thread is read again anyway. */
10
10
  export const RESUME_COMPACTION_IDLE_MS = 70 * 60_000;
11
11
 
12
+ /* `cache-expired` when the CLI named when its prompt cache goes cold, `idle` when only the silence says so. */
13
+ export type ResumeCompactionReason = 'cache-expired' | 'idle';
14
+
12
15
  export interface ResumeCompactionOffer {
13
16
  /* The turn the offer is about, and the key "Keep full history" is remembered under. */
14
17
  turnId: string;
15
18
  tokens: number;
19
+ reason: ResumeCompactionReason;
16
20
  }
17
21
 
18
- function lastTurn(items: readonly ChatItem[]): Extract<ChatItem, { kind: 'turn' }> | null {
19
- return items.findLast((item): item is Extract<ChatItem, { kind: 'turn' }> => item.kind === 'turn') ?? null;
20
- }
22
+ type Turn = Extract<ChatItem, { kind: 'turn' }>;
21
23
 
22
- /* Whether to offer compacting before the next message. */
23
- export function resumeCompactionOffer({
24
- info,
25
- compaction,
26
- items,
27
- dismissedTurnId,
28
- now
29
- }: {
24
+ interface OfferInput {
30
25
  info: ChatInfo;
31
26
  /* Absent until the host answered `provider.list`. */
32
27
  compaction: ProviderCapabilities['compaction'] | undefined;
33
28
  items: readonly ChatItem[];
34
29
  dismissedTurnId: string | null;
35
- now: number;
36
- }): ResumeCompactionOffer | null {
30
+ }
31
+
32
+ /* The newest turn by when it started, so a thread that arrives out of order still names the turn that ran last. */
33
+ function lastTurn(items: readonly ChatItem[]): Turn | null {
34
+ let last: Turn | null = null;
35
+ for (const item of items) {
36
+ if (item.kind === 'turn' && (last === null || item.createdAt >= last.createdAt)) {
37
+ last = item;
38
+ }
39
+ }
40
+ return last;
41
+ }
42
+
43
+ /* The turn an offer would be about, or null while nothing could be offered whatever the time. */
44
+ function offerableTurn({ info, compaction, items, dismissedTurnId }: OfferInput): Turn | null {
37
45
  if (compaction === undefined || compaction === 'none') {
38
46
  return null;
39
47
  }
@@ -47,9 +55,6 @@ export function resumeCompactionOffer({
47
55
  if (turn === null || turn.endedAt === null || turn.id === dismissedTurnId) {
48
56
  return null;
49
57
  }
50
- if (now - turn.endedAt < RESUME_COMPACTION_IDLE_MS) {
51
- return null;
52
- }
53
58
  /*
54
59
  * `contextTokens` keeps the old number until the next request, so without this the offer would
55
60
  * come straight back after a compaction. A native compaction is a turn without a message of the
@@ -58,5 +63,78 @@ export function resumeCompactionOffer({
58
63
  if (items.some((item) => item.kind === 'compaction' && item.turnId === turn.id)) {
59
64
  return null;
60
65
  }
61
- return { turnId: turn.id, tokens: info.usage.contextTokens };
66
+ return turn;
67
+ }
68
+
69
+ /* A silence longer than any cache lives stands in for an expiry from a CLI that names none. */
70
+ function dueAtOf(info: ChatInfo, endedAt: number): number {
71
+ return info.usage.cacheExpiresAt ?? endedAt + RESUME_COMPACTION_IDLE_MS;
72
+ }
73
+
74
+ /*
75
+ * When the next message would pay for the whole thread again, or null while nothing could be offered,
76
+ * so a surface knows whether to keep a timer.
77
+ */
78
+ export function resumeCompactionDueAt(input: OfferInput): number | null {
79
+ const endedAt = offerableTurn(input)?.endedAt ?? null;
80
+ return endedAt === null ? null : dueAtOf(input.info, endedAt);
81
+ }
82
+
83
+ /* Whether to offer compacting before the next message. */
84
+ export function resumeCompactionOffer(input: OfferInput & { now: number }): ResumeCompactionOffer | null {
85
+ const turn = offerableTurn(input);
86
+ if (turn === null || turn.endedAt === null || input.now < dueAtOf(input.info, turn.endedAt)) {
87
+ return null;
88
+ }
89
+ return {
90
+ turnId: turn.id,
91
+ tokens: input.info.usage.contextTokens,
92
+ reason: input.info.usage.cacheExpiresAt === undefined ? 'idle' : 'cache-expired'
93
+ };
94
+ }
95
+
96
+ /* What `watchDueAt` keeps time with; a test hands in a fake. */
97
+ export interface DueClock {
98
+ now(): number;
99
+ setTimer(run: () => void, ms: number): unknown;
100
+ clearTimer(timer: unknown): void;
101
+ /* Calls `wake` when the page may have slept or sat in the background, and answers how to stop. */
102
+ onWake(wake: () => void): () => void;
103
+ }
104
+
105
+ /*
106
+ * Calls `due` with the time once `dueAt` passed. A timer can fire a few milliseconds early, and one that runs on a
107
+ * monotonic clock fires late after the system slept, so an early timer is armed again and a wake looks at once.
108
+ */
109
+ export function watchDueAt(dueAt: number, due: (now: number) => void, clock: DueClock): () => void {
110
+ let timer: unknown = null;
111
+ let done = false;
112
+ const check = (): void => {
113
+ if (timer !== null) {
114
+ clock.clearTimer(timer);
115
+ timer = null;
116
+ }
117
+ if (done) {
118
+ return;
119
+ }
120
+ const now = clock.now();
121
+ if (now >= dueAt) {
122
+ done = true;
123
+ due(now);
124
+ return;
125
+ }
126
+ timer = clock.setTimer(() => {
127
+ timer = null;
128
+ check();
129
+ }, dueAt - now);
130
+ };
131
+ const stopWake = clock.onWake(check);
132
+ check();
133
+ return () => {
134
+ done = true;
135
+ stopWake();
136
+ if (timer !== null) {
137
+ clock.clearTimer(timer);
138
+ }
139
+ };
62
140
  }
@@ -125,12 +125,13 @@ export function summarizeGroup(tools: ChatToolItem[], looks?: readonly CommandLo
125
125
  }
126
126
  const names = new Set(tools.map((tool) => tool.name));
127
127
  const only = names.size === 1 ? tools[0]!.name : null;
128
- if (only !== null && toolEntry(only)?.grouped === true) {
128
+ const failed = tools.filter(failedFileChange);
129
+ if (only !== null && toolEntry(only)?.grouped === true && failed.length === 0) {
129
130
  return i18next.t(`agent-chat:group.tools.${only}`, { count: tools.length });
130
131
  }
131
132
  // Counts the files rather than the calls: three edits to one file are one file edited.
132
- if (tools.every((tool) => isFileChange(tool.name))) {
133
- const paths = new Set(tools.map(editedPath));
133
+ if (tools.every((tool) => isFileChange(tool.name)) && failed.length < tools.length) {
134
+ const paths = new Set(tools.filter((tool) => !failedFileChange(tool)).map(editedPath));
134
135
  return i18next.t('agent-chat:group.edited', { count: paths.size });
135
136
  }
136
137
  if (only !== null) {
@@ -139,6 +140,11 @@ export function summarizeGroup(tools: ChatToolItem[], looks?: readonly CommandLo
139
140
  return i18next.t('agent-chat:group.toolCalls', { count: tools.length });
140
141
  }
141
142
 
143
+ /* A file change that failed or was refused changed nothing: it counts as a call, never as an edit. */
144
+ function failedFileChange(tool: ChatToolItem): boolean {
145
+ return isFileChange(tool.name) && tool.state === 'error';
146
+ }
147
+
142
148
  function editedPath(tool: ChatToolItem): string {
143
149
  return (tool.input as { file_path?: string })?.file_path ?? tool.id;
144
150
  }
@@ -168,6 +174,8 @@ export function summarizeTurn(tools: readonly ChatToolItem[], looks?: readonly C
168
174
  if (run !== null && group !== undefined) {
169
175
  // Keyed by the program too, so two looks with a key in common keep their own sentences.
170
176
  add(`look:${run.look.program}:${group.key}`, group.sentence);
177
+ } else if (failedFileChange(tool)) {
178
+ other += 1;
171
179
  } else if (isFileChange(tool.name)) {
172
180
  edited.add(editedPath(tool));
173
181
  add('agent-chat:group.edited', (count) => i18next.t('agent-chat:group.edited', { count }), edited.size);
@@ -20,6 +20,8 @@ export interface ChatPreferences {
20
20
  readingWidth: ChatReadingWidth;
21
21
  /* Which key sends from the composer. It never reaches a machine either. */
22
22
  sendKey: ChatSendKey;
23
+ /* The models a person starred, per CLI, which the pickers list first. Neither does this. */
24
+ favoriteModels: Partial<Record<AgentKind, string[]>>;
23
25
  /* When a person last changed any of it; what settles whose pick a machine uses when several clients told it one. */
24
26
  changedAt: number;
25
27
  }
@@ -32,9 +34,23 @@ export const DEFAULT_CHAT_PREFERENCES: ChatPreferences = {
32
34
  accountByMachine: {},
33
35
  readingWidth: DEFAULT_READING_WIDTH,
34
36
  sendKey: DEFAULT_SEND_KEY,
37
+ favoriteModels: {},
35
38
  changedAt: 0
36
39
  };
37
40
 
41
+ /* Only lists of slugs survive, so a hand-edited or older record cannot put something else in the pickers. */
42
+ function parseFavoriteModels(stored: unknown): Partial<Record<AgentKind, string[]>> {
43
+ if (typeof stored !== 'object' || stored === null || Array.isArray(stored)) {
44
+ return {};
45
+ }
46
+ return Object.fromEntries(
47
+ Object.entries(stored).flatMap(([provider, slugs]) => {
48
+ const kept = Array.isArray(slugs) ? slugs.filter((slug): slug is string => typeof slug === 'string' && slug !== '') : [];
49
+ return kept.length === 0 ? [] : [[provider, kept]];
50
+ })
51
+ );
52
+ }
53
+
38
54
  /*
39
55
  * Only the fields we know today, so what an older build wrote (one global `selection`, the plan
40
56
  * toggle's `interactionMode`) falls away here instead of riding along in every later write. A
@@ -54,6 +70,7 @@ export function parseChatPreferences(raw: string | null): ChatPreferences {
54
70
  accountByMachine: { ...stored.accountByMachine },
55
71
  readingWidth: isChatReadingWidth(stored.readingWidth) ? stored.readingWidth : DEFAULT_READING_WIDTH,
56
72
  sendKey: isChatSendKey(stored.sendKey) ? stored.sendKey : DEFAULT_SEND_KEY,
73
+ favoriteModels: parseFavoriteModels(stored.favoriteModels),
57
74
  changedAt: typeof stored.changedAt === 'number' ? stored.changedAt : 0
58
75
  };
59
76
  } catch {
@@ -190,6 +207,24 @@ export function rememberChatAccount(scopeId: string, provider: AgentKind, accoun
190
207
  write(withAccount(useChatPreferences.getState(), scopeId, provider, account));
191
208
  }
192
209
 
210
+ /* The starred models of one CLI; a slug only means something inside its own catalog. */
211
+ export function favoriteModelsOf(favorites: ChatPreferences['favoriteModels'], provider: AgentKind): ReadonlySet<string> {
212
+ return new Set(favorites[provider] ?? []);
213
+ }
214
+
215
+ /* Stars a model that was not, and takes the star off one that was. A provider left without any drops out. */
216
+ export function withFavoriteToggled(preferences: ChatPreferences, provider: AgentKind, slug: string): ChatPreferences {
217
+ const current = preferences.favoriteModels[provider] ?? [];
218
+ const next = current.includes(slug) ? current.filter((each) => each !== slug) : [...current, slug];
219
+ const { [provider]: _previous, ...others } = preferences.favoriteModels;
220
+ return { ...preferences, favoriteModels: next.length === 0 ? others : { ...others, [provider]: next } };
221
+ }
222
+
223
+ /* Leaves `changedAt` alone: a star orders a person's own pickers and is no pick a machine weighs. */
224
+ export function toggleFavoriteModel(provider: AgentKind, slug: string): void {
225
+ store(withFavoriteToggled(useChatPreferences.getState(), provider, slug));
226
+ }
227
+
193
228
  /* Back to the CLI's own default for this provider; the other providers keep what they had. */
194
229
  export function forgetChatSelection(provider: AgentKind): void {
195
230
  const current = useChatPreferences.getState();
@@ -1,5 +1,4 @@
1
1
  import i18next from 'i18next';
2
- import { useMemo } from 'react';
3
2
  import { create } from 'zustand';
4
3
  import type { ChatItem, ChatSubagentItem } from '@adecore/agent-contracts';
5
4
  import { useChatScope } from '../scope';
@@ -128,10 +127,10 @@ export function useOpenableSubagents(chatId: string): ChatSubagentItem[] {
128
127
  // The structure and not the items, so a delta growing a reply does not walk the thread again.
129
128
  const order = useChatRow(chatId, (row) => row?.order);
130
129
  const structure = useChatRow(chatId, (row) => row?.structure);
131
- return useMemo(() => (structure === undefined ? [] : openableSubagents(order ?? NO_ITEMS, structure, refused)), [order, structure, refused]);
130
+ return structure === undefined ? [] : openableSubagents(order ?? NO_ITEMS, structure, refused);
132
131
  }
133
132
 
134
133
  export function useSubagentItem(chatId: string, toolUseId: string): ChatSubagentItem | null {
135
134
  const structure = useChatRow(chatId, (row) => row?.structure);
136
- return useMemo(() => (structure === undefined ? null : subagentOf(structure, toolUseId)), [structure, toolUseId]);
135
+ return structure === undefined ? null : subagentOf(structure, toolUseId);
137
136
  }
@@ -1,10 +1,9 @@
1
- import { useMemo } from 'react';
2
1
  import { useTranslation } from 'react-i18next';
3
2
  import { parseAnsi } from '../logic/ansi';
4
3
 
5
4
  export function AnsiOutput({ text, limit, tailLines }: { text: string; limit?: number; tailLines?: number }) {
6
5
  const { t } = useTranslation('agent-chat');
7
- const { tokens, omitted } = useMemo(() => parseAnsi(text, { limit, tailLines }), [text, limit, tailLines]);
6
+ const { tokens, omitted } = parseAnsi(text, { limit, tailLines });
8
7
  return (
9
8
  <>
10
9
  {tokens.map((token, index) => (
@@ -1,4 +1,3 @@
1
- import { useMemo } from 'react';
2
1
  import clsx from 'clsx';
3
2
  import { useTranslation } from 'react-i18next';
4
3
  import { Bookmark, Pencil, X } from 'lucide-react';
@@ -8,6 +7,7 @@ import { useChatHost } from '../../host';
8
7
  import { useChatScope } from '../../scope';
9
8
  import { useChatRow } from '../../state/chats';
10
9
  import { Icon, Menu } from '@adecore/ui';
10
+ import { useHostChatPlace } from '../../host-hooks';
11
11
 
12
12
  /* A chat's bookmarks in thread order, each a jump to its message with rename and remove; nothing without bookmarks. */
13
13
  export function BookmarkSubmenu({ chatId }: { chatId: string }) {
@@ -16,8 +16,8 @@ export function BookmarkSubmenu({ chatId }: { chatId: string }) {
16
16
  const bookmarks = useChatRow(chatId, (row) => row?.bookmarks);
17
17
  const order = useChatRow(chatId, (row) => row?.order);
18
18
  const host = useChatHost();
19
- const place = host.useChatPlace(chatId);
20
- const sorted = useMemo(() => bookmarksInThreadOrder(bookmarks ?? [], order ?? []), [bookmarks, order]);
19
+ const place = useHostChatPlace(chatId);
20
+ const sorted = bookmarksInThreadOrder(bookmarks ?? [], order ?? []);
21
21
  if (sorted.length === 0) {
22
22
  return null;
23
23
  }
@@ -1,4 +1,4 @@
1
- import { useMemo, type ReactNode } from 'react';
1
+ import { type ReactNode } from 'react';
2
2
  import clsx from 'clsx';
3
3
  import { useShallow } from 'zustand/shallow';
4
4
  import { useTranslation } from 'react-i18next';
@@ -16,6 +16,7 @@ import { useChatHost } from '../../host';
16
16
  import { useChatScope } from '../../scope';
17
17
  import { useChatActions } from '../actions';
18
18
  import { statusLookOf, type StatusWord } from '../../agents/status-look';
19
+ import { useHostTask, useHostTasks } from '../../host-hooks';
19
20
 
20
21
  const NO_TASKS: readonly ChatBackgroundTask[] = [];
21
22
 
@@ -30,29 +31,21 @@ export function ChatActivity({ chatId }: { chatId: string }) {
30
31
  const order = useChatRow(chatId, (row) => row?.order) ?? NO_ITEMS;
31
32
  const before = useChatRow(chatId, (row) => row?.subagentsBefore) ?? NO_ITEMS;
32
33
  const structure = useChatRow(chatId, (row) => row?.structure);
33
- const subagents = useMemo(() => (structure === undefined ? [] : flyoutSubagents([...before, ...order], structure)), [before, order, structure]);
34
+ const subagents = structure === undefined ? [] : flyoutSubagents([...before, ...order], structure);
34
35
  const { id, keyOf } = useChatScope();
35
- const tasks = host.tasks.useTasks(id);
36
+ const tasks = useHostTasks(id);
36
37
  // The infos of these sub-agents' own chats, so the info of any other chat changing does not draw this again.
37
38
  const children = useChats(useShallow((s) => subagents.map((item) => (item.childId === undefined ? undefined : s.statusByKey[keyOf(item.childId)]?.info))));
38
- const progress = useMemo(
39
- () =>
40
- progressOf(
41
- subagents.map((item, index) => {
42
- const taskId = taskIdOf(item);
43
- return taskRowStateOf(item, taskId === null ? null : (tasks?.[taskId] ?? null), children[index]);
44
- })
45
- ),
46
- [subagents, tasks, children]
47
- );
48
- const words = useMemo(
49
- () =>
50
- subagents.map((item) => {
51
- const taskId = taskIdOf(item);
52
- return statusWordOf(item, taskId === null ? null : (tasks?.[taskId] ?? null));
53
- }),
54
- [subagents, tasks]
39
+ const progress = progressOf(
40
+ subagents.map((item, index) => {
41
+ const taskId = taskIdOf(item);
42
+ return taskRowStateOf(item, taskId === null ? null : (tasks?.[taskId] ?? null), children[index]);
43
+ })
55
44
  );
45
+ const words = subagents.map((item) => {
46
+ const taskId = taskIdOf(item);
47
+ return statusWordOf(item, taskId === null ? null : (tasks?.[taskId] ?? null));
48
+ });
56
49
  const background = useChatRow(chatId, (row) => row?.info.background) ?? NO_TASKS;
57
50
  if (subagents.length === 0 && background.length === 0) {
58
51
  return null;
@@ -174,11 +167,10 @@ function SubagentRows({ chatId, items }: { chatId: string; items: readonly ChatS
174
167
  }
175
168
 
176
169
  function SubagentEntry({ chatId, item, now }: { chatId: string; item: ChatSubagentItem; now: number }) {
177
- const host = useChatHost();
178
170
  const { t } = useTranslation('agent-chat');
179
171
  const { id, keyOf } = useChatScope();
180
172
  const refused = useSubagentSupport((s) => s.unsupported[id] === true);
181
- const task = host.tasks.useTask(id, taskIdOf(item));
173
+ const task = useHostTask(id, taskIdOf(item));
182
174
  const { show } = useSubagentTrail(chatId);
183
175
  const child = useChats((s) => (item.childId === undefined ? undefined : s.statusByKey[keyOf(item.childId)]?.info));
184
176
  const word = statusWordOf(item, task);
@@ -1,15 +1,14 @@
1
1
  import clsx from 'clsx';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { MessageSquare, X } from 'lucide-react';
4
- import { useChatHost } from '../../host';
5
4
  import { CHIP_IN_MESSAGE, MENTION_TONE } from './chips';
6
5
  import { Icon, Tooltip } from '@adecore/ui';
6
+ import { useHostReferableChats } from '../../host-hooks';
7
7
 
8
8
  /* A chat attached to a message, under the title it has now rather than the one it had when it was attached. */
9
9
  export function ChatReferenceChip({ chatId, onRemove }: { chatId: string; onRemove?: () => void }) {
10
- const host = useChatHost();
11
10
  const { t } = useTranslation('agent-chat');
12
- const chat = host.useReferableChats().find((candidate) => candidate.id === chatId);
11
+ const chat = useHostReferableChats().find((candidate) => candidate.id === chatId);
13
12
  const title = chat === undefined ? t('chatReference.gone') : chat.title || t('chatReference.untitled');
14
13
  return (
15
14
  <span className={clsx(MENTION_TONE, CHIP_IN_MESSAGE)}>