@adecore/agents-react 0.39.0 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/dist/agents/AgentModelLabel.js +2 -1
  2. package/dist/agents/model-name.js +2 -1
  3. package/dist/agents/provider-paths.d.ts +2 -1
  4. package/dist/agents/provider-paths.js +7 -3
  5. package/dist/chat/account-choice.js +57 -28
  6. package/dist/chat/bookmarks.js +2 -1
  7. package/dist/chat/drafts.d.ts +0 -1
  8. package/dist/chat/drafts.js +9 -28
  9. package/dist/chat/logic/picker-entries.js +2 -8
  10. package/dist/chat/logic/resume-compaction.js +2 -5
  11. package/dist/chat/logic/timeline-copy.js +2 -8
  12. package/dist/chat/logic/timeline.js +9 -10
  13. package/dist/chat/logic/workflow.js +2 -4
  14. package/dist/chat/subagent-conversation.js +2 -1
  15. package/dist/chat/ui/GoalDock.js +2 -1
  16. package/dist/chat/ui/LimitState.js +4 -6
  17. package/dist/chat/ui/composer/use-continuable-turn.js +0 -1
  18. package/dist/chat/ui/composer/use-notice.js +2 -1
  19. package/dist/chat/ui/composer/use-prompt-history.js +1 -1
  20. package/dist/chat/ui/composer/use-prompt-menus.js +216 -253
  21. package/dist/chat/ui/composer/use-take-back.js +2 -2
  22. package/dist/chat/ui/diff/ApprovalReview.js +1 -1
  23. package/dist/chat/ui/highlighter.d.ts +0 -6
  24. package/dist/chat/ui/highlighter.js +0 -12
  25. package/dist/chat/ui/icons.d.ts +0 -1
  26. package/dist/chat/ui/icons.js +0 -3
  27. package/dist/chat/ui/rows/MessageRows.js +2 -5
  28. package/dist/chat/ui/rows/row-rhythm.js +2 -5
  29. package/dist/chat/ui/theme-watch.js +3 -7
  30. package/dist/prompts/logic/usePromptSession.js +2 -1
  31. package/dist/providers/AddAccountForm.js +2 -1
  32. package/dist/providers/CliDetail.js +104 -130
  33. package/dist/providers/account-actions.js +6 -9
  34. package/dist/state/chats.d.ts +1 -0
  35. package/dist/state/chats.js +6 -11
  36. package/dist/state/provider-accounts.d.ts +1 -1
  37. package/dist/state/provider-accounts.js +2 -8
  38. package/dist/transport.d.ts +2 -0
  39. package/dist/transport.js +14 -0
  40. package/dist/usage/ChartLogo.d.ts +5 -0
  41. package/dist/usage/ChartLogo.js +48 -0
  42. package/dist/usage/LimitsList.js +6 -16
  43. package/dist/usage/UsageBreakdown.js +17 -40
  44. package/dist/usage/UsageLimits.js +183 -211
  45. package/dist/usage/UsagePage.js +3 -17
  46. package/dist/usage/UsageSummary.js +4 -9
  47. package/dist/usage/limits.js +16 -31
  48. package/dist/usage/summary.d.ts +1 -1
  49. package/dist/usage/summary.js +3 -5
  50. package/package.json +27 -27
  51. package/src/agents/AccountDot.tsx +1 -0
  52. package/src/agents/AgentIcon.tsx +2 -0
  53. package/src/agents/AgentModelLabel.tsx +4 -6
  54. package/src/agents/ModelOptionControl.tsx +8 -0
  55. package/src/agents/account-limits.ts +3 -0
  56. package/src/agents/accounts.ts +12 -0
  57. package/src/agents/model-name.ts +13 -1
  58. package/src/agents/model-options.ts +2 -0
  59. package/src/agents/model-requirement.ts +4 -0
  60. package/src/agents/option-words.ts +2 -0
  61. package/src/agents/provider-paths.ts +8 -3
  62. package/src/agents/status-look.ts +1 -0
  63. package/src/chat/account-choice.ts +25 -19
  64. package/src/chat/actions.ts +5 -0
  65. package/src/chat/attachments.ts +8 -0
  66. package/src/chat/bookmarks.ts +13 -1
  67. package/src/chat/chat-client.ts +63 -0
  68. package/src/chat/chat-references.ts +3 -0
  69. package/src/chat/composer-effort.ts +5 -0
  70. package/src/chat/composer-send.ts +4 -0
  71. package/src/chat/drafts.ts +21 -17
  72. package/src/chat/forks.ts +4 -0
  73. package/src/chat/guards.ts +2 -0
  74. package/src/chat/index.ts +7 -0
  75. package/src/chat/logic/ansi.ts +47 -0
  76. package/src/chat/logic/bookmarks.ts +7 -0
  77. package/src/chat/logic/command-looks.ts +29 -0
  78. package/src/chat/logic/context-usage.ts +3 -0
  79. package/src/chat/logic/continue-after-stop.ts +5 -0
  80. package/src/chat/logic/effort.ts +4 -0
  81. package/src/chat/logic/embedded-code.ts +63 -0
  82. package/src/chat/logic/fork.ts +22 -0
  83. package/src/chat/logic/generated-image.ts +7 -0
  84. package/src/chat/logic/goal.ts +7 -0
  85. package/src/chat/logic/handback.ts +2 -0
  86. package/src/chat/logic/limit.ts +8 -0
  87. package/src/chat/logic/picker-entries.ts +16 -4
  88. package/src/chat/logic/resume-compaction.ts +22 -5
  89. package/src/chat/logic/scrubber.ts +38 -5
  90. package/src/chat/logic/thought.ts +5 -0
  91. package/src/chat/logic/thread-cards.ts +6 -0
  92. package/src/chat/logic/timeline-copy.ts +4 -9
  93. package/src/chat/logic/timeline-target.ts +15 -0
  94. package/src/chat/logic/timeline.ts +113 -10
  95. package/src/chat/logic/tools.ts +30 -0
  96. package/src/chat/logic/visual-height.ts +5 -0
  97. package/src/chat/logic/visual-viewport.ts +1 -0
  98. package/src/chat/logic/welcome.ts +3 -0
  99. package/src/chat/logic/workflow.ts +26 -7
  100. package/src/chat/mentions.ts +56 -0
  101. package/src/chat/pending-prompts.ts +16 -0
  102. package/src/chat/persisted-json.ts +15 -0
  103. package/src/chat/preferences.ts +16 -0
  104. package/src/chat/prompt-hold.ts +2 -0
  105. package/src/chat/quote.ts +7 -0
  106. package/src/chat/reading-width.ts +1 -0
  107. package/src/chat/recent-messages.ts +5 -0
  108. package/src/chat/resume-compaction-dismissals.ts +1 -0
  109. package/src/chat/stash.ts +12 -0
  110. package/src/chat/subagent-conversation.ts +24 -4
  111. package/src/chat/subagent-list.ts +14 -0
  112. package/src/chat/subagent-support.ts +1 -0
  113. package/src/chat/subagent-view.ts +9 -0
  114. package/src/chat/thumbnails.ts +14 -0
  115. package/src/chat/timeline-flash.ts +2 -0
  116. package/src/chat/timeline-scroll.ts +23 -0
  117. package/src/chat/ui/AccountPill.tsx +4 -0
  118. package/src/chat/ui/AnsiOutput.tsx +1 -0
  119. package/src/chat/ui/BookmarkMarker.tsx +5 -0
  120. package/src/chat/ui/BookmarkSubmenu.tsx +1 -0
  121. package/src/chat/ui/ChatActivity.tsx +13 -0
  122. package/src/chat/ui/CodeBlock.tsx +8 -0
  123. package/src/chat/ui/Composer.tsx +15 -0
  124. package/src/chat/ui/ComposerInput.tsx +22 -0
  125. package/src/chat/ui/DiffPool.tsx +3 -0
  126. package/src/chat/ui/EditDiff.tsx +5 -0
  127. package/src/chat/ui/GoalDock.tsx +13 -1
  128. package/src/chat/ui/ImageView.tsx +11 -0
  129. package/src/chat/ui/LimitState.tsx +23 -6
  130. package/src/chat/ui/Markdown.tsx +41 -0
  131. package/src/chat/ui/MermaidBlock.tsx +16 -0
  132. package/src/chat/ui/MessageActions.tsx +8 -0
  133. package/src/chat/ui/ModelMarks.tsx +1 -0
  134. package/src/chat/ui/Pickers.tsx +26 -0
  135. package/src/chat/ui/PromptComposer.tsx +2 -0
  136. package/src/chat/ui/PromptEditor.tsx +6 -0
  137. package/src/chat/ui/QuoteButton.tsx +12 -0
  138. package/src/chat/ui/ReferenceChip.tsx +3 -0
  139. package/src/chat/ui/ResumeCompactionDock.tsx +1 -0
  140. package/src/chat/ui/RowTime.tsx +1 -0
  141. package/src/chat/ui/RunSettings.tsx +38 -0
  142. package/src/chat/ui/Scrubber.tsx +14 -0
  143. package/src/chat/ui/SessionLine.tsx +1 -0
  144. package/src/chat/ui/ShellCommand.tsx +32 -0
  145. package/src/chat/ui/SubagentControls.tsx +7 -0
  146. package/src/chat/ui/SubagentInfo.tsx +1 -0
  147. package/src/chat/ui/SubagentStopButton.tsx +6 -0
  148. package/src/chat/ui/SubagentTimeline.tsx +10 -0
  149. package/src/chat/ui/Timeline.tsx +119 -0
  150. package/src/chat/ui/TimelineMenu.tsx +5 -0
  151. package/src/chat/ui/UnifiedDiff.tsx +6 -0
  152. package/src/chat/ui/UploadThumb.tsx +2 -0
  153. package/src/chat/ui/VisualDialogs.tsx +3 -0
  154. package/src/chat/ui/VisualFrame.tsx +15 -0
  155. package/src/chat/ui/Welcome.tsx +1 -0
  156. package/src/chat/ui/chip-clipboard.ts +20 -0
  157. package/src/chat/ui/code-lines.ts +14 -0
  158. package/src/chat/ui/code-theme.ts +1 -0
  159. package/src/chat/ui/composer/ComposerControls.tsx +5 -0
  160. package/src/chat/ui/composer/ComposerMenu.tsx +12 -0
  161. package/src/chat/ui/composer/DraftParts.tsx +2 -0
  162. package/src/chat/ui/composer/QueuedMessages.tsx +4 -0
  163. package/src/chat/ui/composer/chip-paste.ts +16 -0
  164. package/src/chat/ui/composer/chips.ts +6 -0
  165. package/src/chat/ui/composer/editor.ts +9 -0
  166. package/src/chat/ui/composer/insert.ts +2 -0
  167. package/src/chat/ui/composer/key-handler.ts +39 -0
  168. package/src/chat/ui/composer/keys.ts +23 -0
  169. package/src/chat/ui/composer/placeholder.ts +6 -0
  170. package/src/chat/ui/composer/prompt-picks.ts +1 -0
  171. package/src/chat/ui/composer/surface.ts +5 -0
  172. package/src/chat/ui/composer/use-chat-controls.ts +13 -0
  173. package/src/chat/ui/composer/use-compaction-offer.ts +3 -0
  174. package/src/chat/ui/composer/use-composer-menus.ts +2 -0
  175. package/src/chat/ui/composer/use-composer-submit.ts +27 -0
  176. package/src/chat/ui/composer/use-continuable-turn.ts +3 -5
  177. package/src/chat/ui/composer/use-draft-files.ts +24 -0
  178. package/src/chat/ui/composer/use-effort-stepper.ts +8 -0
  179. package/src/chat/ui/composer/use-notice.ts +4 -1
  180. package/src/chat/ui/composer/use-pending-requests.ts +3 -0
  181. package/src/chat/ui/composer/use-prompt-history.ts +20 -11
  182. package/src/chat/ui/composer/use-prompt-hold.ts +3 -0
  183. package/src/chat/ui/composer/use-prompt-menus.ts +34 -14
  184. package/src/chat/ui/composer/use-take-back.ts +20 -2
  185. package/src/chat/ui/diff/ApprovalReview.tsx +18 -15
  186. package/src/chat/ui/diff/DiffFallbackBoundary.tsx +1 -0
  187. package/src/chat/ui/diff/DiffOmitted.tsx +1 -0
  188. package/src/chat/ui/diff/DiffStack.tsx +2 -0
  189. package/src/chat/ui/diff/DiffUnavailable.tsx +1 -0
  190. package/src/chat/ui/diff/EditorEditDiff.tsx +13 -0
  191. package/src/chat/ui/diff/EditorPatchDiff.tsx +4 -0
  192. package/src/chat/ui/diff/LegacyDiffPool.tsx +2 -0
  193. package/src/chat/ui/diff/LegacyEditDiff.tsx +2 -0
  194. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +8 -0
  195. package/src/chat/ui/diff/RawDiff.tsx +1 -0
  196. package/src/chat/ui/diff/ReviewDiff.tsx +1 -0
  197. package/src/chat/ui/diff/diff-source-budget.ts +8 -0
  198. package/src/chat/ui/diff/editor-edit-model.ts +4 -0
  199. package/src/chat/ui/diff/review-plan.ts +9 -0
  200. package/src/chat/ui/diff/review-settle.ts +1 -0
  201. package/src/chat/ui/diff/syntax-runtime.ts +52 -0
  202. package/src/chat/ui/diff/use-review-stack.ts +9 -0
  203. package/src/chat/ui/diff-syntax.ts +12 -0
  204. package/src/chat/ui/diff-theme.ts +2 -0
  205. package/src/chat/ui/file-links.ts +2 -0
  206. package/src/chat/ui/find-reveal.ts +1 -0
  207. package/src/chat/ui/full-diff.ts +1 -0
  208. package/src/chat/ui/highlighter.ts +29 -15
  209. package/src/chat/ui/icons.tsx +2 -4
  210. package/src/chat/ui/index.ts +4 -0
  211. package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +25 -0
  212. package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +1 -0
  213. package/src/chat/ui/intelligent-ui/UiReply.tsx +63 -0
  214. package/src/chat/ui/intelligent-ui/block-local.ts +5 -0
  215. package/src/chat/ui/intelligent-ui/chart-data.ts +15 -0
  216. package/src/chat/ui/intelligent-ui/node-text.ts +17 -0
  217. package/src/chat/ui/intelligent-ui/renderers/binding.ts +1 -0
  218. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +31 -0
  219. package/src/chat/ui/intelligent-ui/renderers/choices.tsx +4 -0
  220. package/src/chat/ui/intelligent-ui/renderers/content.tsx +18 -0
  221. package/src/chat/ui/intelligent-ui/renderers/data.tsx +11 -0
  222. package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +13 -0
  223. package/src/chat/ui/intelligent-ui/renderers/links.tsx +9 -0
  224. package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +15 -0
  225. package/src/chat/ui/intelligent-ui/renderers/map-style.ts +15 -0
  226. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +48 -0
  227. package/src/chat/ui/intelligent-ui/renderers/map.tsx +20 -0
  228. package/src/chat/ui/intelligent-ui/renderers/place-point.ts +3 -0
  229. package/src/chat/ui/intelligent-ui/renderers/structure.tsx +3 -0
  230. package/src/chat/ui/intelligent-ui/renderers/text.tsx +8 -0
  231. package/src/chat/ui/intelligent-ui/reply-parts.ts +5 -0
  232. package/src/chat/ui/intelligent-ui/table-data.ts +17 -0
  233. package/src/chat/ui/intelligent-ui/use-in-view.ts +4 -0
  234. package/src/chat/ui/intelligent-ui/use-later.ts +4 -0
  235. package/src/chat/ui/intelligent-ui/use-truncated.ts +5 -0
  236. package/src/chat/ui/intelligent-ui/use-ui-links.ts +29 -0
  237. package/src/chat/ui/intelligent-ui/use-ui-queries.ts +60 -0
  238. package/src/chat/ui/markdown-blocks.ts +64 -0
  239. package/src/chat/ui/mermaid.ts +4 -0
  240. package/src/chat/ui/quote-selection.ts +10 -0
  241. package/src/chat/ui/rehype-chips.ts +5 -0
  242. package/src/chat/ui/rehype-fade.ts +2 -0
  243. package/src/chat/ui/remark-html-as-text.ts +5 -0
  244. package/src/chat/ui/reveal.ts +18 -0
  245. package/src/chat/ui/rows/ForksRow.tsx +5 -0
  246. package/src/chat/ui/rows/GeneratedImageRow.tsx +10 -0
  247. package/src/chat/ui/rows/MessageRows.tsx +29 -5
  248. package/src/chat/ui/rows/RowContainer.tsx +1 -0
  249. package/src/chat/ui/rows/Rows.tsx +2 -0
  250. package/src/chat/ui/rows/SubagentRow.tsx +13 -0
  251. package/src/chat/ui/rows/TaskRow.tsx +7 -0
  252. package/src/chat/ui/rows/VisualRow.tsx +4 -0
  253. package/src/chat/ui/rows/WorkRows.tsx +46 -0
  254. package/src/chat/ui/rows/WorkflowRow.tsx +10 -0
  255. package/src/chat/ui/rows/row-rhythm.ts +7 -5
  256. package/src/chat/ui/theme-watch.ts +8 -7
  257. package/src/chat/ui/thread-references.ts +7 -0
  258. package/src/chat/ui/use-diff-source-tokens.ts +8 -0
  259. package/src/chat/ui/useToggleSet.ts +3 -0
  260. package/src/chat/ui/visual-theme.ts +30 -0
  261. package/src/chat/visual-bridge.ts +23 -0
  262. package/src/chat/visual-viewport.ts +12 -0
  263. package/src/host-hooks.ts +6 -4
  264. package/src/host.ts +2 -0
  265. package/src/index.ts +3 -0
  266. package/src/lazy.tsx +9 -0
  267. package/src/mounted-registry.ts +4 -0
  268. package/src/port-transport.ts +32 -0
  269. package/src/prompts/index.ts +2 -0
  270. package/src/prompts/logic/focus.ts +5 -0
  271. package/src/prompts/logic/keys.ts +12 -0
  272. package/src/prompts/logic/prompts.ts +8 -0
  273. package/src/prompts/logic/subjects.ts +8 -0
  274. package/src/prompts/logic/useFocusAfterAnswer.ts +2 -0
  275. package/src/prompts/logic/usePromptSession.ts +13 -1
  276. package/src/prompts/ui/ApprovalBody.tsx +2 -0
  277. package/src/prompts/ui/PromptActions.tsx +2 -0
  278. package/src/prompts/ui/PromptCard.tsx +6 -0
  279. package/src/prompts/ui/PromptView.tsx +6 -0
  280. package/src/prompts/ui/QuestionBody.tsx +11 -0
  281. package/src/providers/AccountColors.tsx +1 -0
  282. package/src/providers/AccountDetail.tsx +7 -0
  283. package/src/providers/AccountVariables.tsx +2 -0
  284. package/src/providers/AddAccountForm.tsx +5 -1
  285. package/src/providers/CliDetail.tsx +6 -5
  286. package/src/providers/InstallPage.tsx +11 -0
  287. package/src/providers/ProviderList.tsx +3 -0
  288. package/src/providers/ProvidersPane.tsx +18 -0
  289. package/src/providers/account-actions.ts +10 -8
  290. package/src/providers/install-steps.ts +3 -0
  291. package/src/providers/pane-target.ts +5 -0
  292. package/src/providers/provider-abilities.ts +1 -0
  293. package/src/providers/provider-groups.ts +6 -0
  294. package/src/providers/variables.ts +12 -0
  295. package/src/scope.ts +2 -0
  296. package/src/state/chats.ts +61 -14
  297. package/src/state/index.ts +2 -0
  298. package/src/state/provider-accounts.ts +7 -9
  299. package/src/state/providers.ts +1 -0
  300. package/src/state/usage.ts +6 -0
  301. package/src/storage.ts +18 -0
  302. package/src/testing/prompts.ts +5 -0
  303. package/src/transport.ts +18 -0
  304. package/src/usage/ChartLogo.tsx +12 -0
  305. package/src/usage/LimitsList.tsx +7 -8
  306. package/src/usage/UsageBreakdown.tsx +11 -4
  307. package/src/usage/UsageChart.tsx +5 -0
  308. package/src/usage/UsageLimits.tsx +10 -7
  309. package/src/usage/UsageLimitsCard.tsx +2 -0
  310. package/src/usage/UsagePage.tsx +16 -19
  311. package/src/usage/UsageSummary.tsx +4 -5
  312. package/src/usage/UsageTiles.tsx +2 -0
  313. package/src/usage/format.ts +7 -0
  314. package/src/usage/limit-groups.ts +14 -0
  315. package/src/usage/limits.ts +11 -13
  316. package/src/usage/money.ts +1 -0
  317. package/src/usage/summary.ts +15 -7
@@ -39,6 +39,7 @@ export function BookmarkMarker({ chatId, itemId, bookmark }: BookmarkMarkerProps
39
39
  initial={bookmark?.name ?? ''}
40
40
  onDone={(name) => {
41
41
  useBookmarkNaming.getState().close();
42
+
42
43
  if (name !== null) {
43
44
  void nameBookmark(scope, host, chatId, itemId, name, bookmark?.name);
44
45
  }
@@ -73,6 +74,7 @@ function NameField({ initial, onDone }: { initial: string; onDone: (name: string
73
74
  useEffect(() => {
74
75
  ref.current?.focus();
75
76
  const frame = requestAnimationFrame(() => ref.current?.focus());
77
+
76
78
  return () => cancelAnimationFrame(frame);
77
79
  }, []);
78
80
 
@@ -80,6 +82,7 @@ function NameField({ initial, onDone }: { initial: string; onDone: (name: string
80
82
  if (settled.current) {
81
83
  return;
82
84
  }
85
+
83
86
  settled.current = true;
84
87
  onDone(name);
85
88
  };
@@ -98,9 +101,11 @@ function NameField({ initial, onDone }: { initial: string; onDone: (name: string
98
101
  onBlur={(e) => settle(e.currentTarget.value)}
99
102
  onKeyDown={(e) => {
100
103
  e.stopPropagation();
104
+
101
105
  if (e.key === 'Enter') {
102
106
  settle(e.currentTarget.value);
103
107
  }
108
+
104
109
  if (e.key === 'Escape') {
105
110
  settle(null);
106
111
  }
@@ -18,6 +18,7 @@ export function BookmarkSubmenu({ chatId }: { chatId: string }) {
18
18
  const host = useChatHost();
19
19
  const place = useHostChatPlace(chatId);
20
20
  const sorted = bookmarksInThreadOrder(bookmarks ?? [], order ?? []);
21
+
21
22
  if (sorted.length === 0) {
22
23
  return null;
23
24
  }
@@ -36,28 +36,37 @@ export function ChatActivity({ chatId }: { chatId: string }) {
36
36
  const tasks = useHostTasks(id);
37
37
  // The infos of these sub-agents' own chats, so the info of any other chat changing does not draw this again.
38
38
  const children = useChats(useShallow((s) => subagents.map((item) => (item.childId === undefined ? undefined : s.statusByKey[keyOf(item.childId)]?.info))));
39
+
39
40
  const progress = progressOf(
40
41
  subagents.map((item, index) => {
41
42
  const taskId = taskIdOf(item);
43
+
42
44
  return taskRowStateOf(item, taskId === null ? null : (tasks?.[taskId] ?? null), children[index]);
43
45
  })
44
46
  );
47
+
45
48
  const words = subagents.map((item) => {
46
49
  const taskId = taskIdOf(item);
50
+
47
51
  return statusWordOf(item, taskId === null ? null : (tasks?.[taskId] ?? null));
48
52
  });
53
+
49
54
  const background = useChatRow(chatId, (row) => row?.info.background) ?? NO_TASKS;
55
+
50
56
  if (subagents.length === 0 && background.length === 0) {
51
57
  return null;
52
58
  }
59
+
53
60
  const { shells, monitors } = backgroundCounts(background);
54
61
  const Slot = host.SubagentSlot;
55
62
  const counting = progress.active > 0 && progress.total > 1;
63
+
56
64
  const label = counting
57
65
  ? [t('activity.progress', { done: progress.done, total: progress.total }), progress.waiting > 0 && t('activity.needsYou', { count: progress.waiting })]
58
66
  .filter(Boolean)
59
67
  .join(', ')
60
68
  : t('activity.subagents', { count: badgeCountOf(words) });
69
+
61
70
  return (
62
71
  <div className="mb-3 flex flex-wrap items-center gap-2">
63
72
  {subagents.length > 0 &&
@@ -94,6 +103,7 @@ export function ChatActivity({ chatId }: { chatId: string }) {
94
103
 
95
104
  export function StatusIcon({ word }: { word: StatusWord }) {
96
105
  const look = statusLookOf(word);
106
+
97
107
  return <Icon icon={look.icon} size={14} className={clsx('shrink-0', look.tone, look.spins && 'animate-spin')} />;
98
108
  }
99
109
 
@@ -142,6 +152,7 @@ function ActivityRow({
142
152
  {time !== '' && <span className="shrink-0 text-xs text-text-faint tabular-nums">{time}</span>}
143
153
  </>
144
154
  );
155
+
145
156
  // The stop is a button of its own beside the line, since a button cannot hold another.
146
157
  return (
147
158
  <div className={clsx('flex items-center gap-1 rounded-md', onOpen && 'hover:bg-surface-hover')}>
@@ -163,6 +174,7 @@ function SubagentRows({ chatId, items }: { chatId: string; items: readonly ChatS
163
174
  1000,
164
175
  items.some((item) => item.status === 'running')
165
176
  );
177
+
166
178
  return items.map((item) => <SubagentEntry key={item.id} chatId={chatId} item={item} now={now} />);
167
179
  }
168
180
 
@@ -175,6 +187,7 @@ function SubagentEntry({ chatId, item, now }: { chatId: string; item: ChatSubage
175
187
  const child = useChats((s) => (item.childId === undefined ? undefined : s.statusByKey[keyOf(item.childId)]?.info));
176
188
  const word = statusWordOf(item, task);
177
189
  const state = taskRowStateOf(item, task, child);
190
+
178
191
  return (
179
192
  <ActivityRow
180
193
  icon={<StatusIcon word={word} />}
@@ -51,7 +51,9 @@ export function CodeBlock({ code, lang, actions, view }: { code: string; lang: s
51
51
  if (copyState !== 'copied') {
52
52
  return;
53
53
  }
54
+
54
55
  const timer = setTimeout(() => setCopyState('idle'), COPIED_MS);
56
+
55
57
  return () => clearTimeout(timer);
56
58
  }, [copyState]);
57
59
 
@@ -59,8 +61,10 @@ export function CodeBlock({ code, lang, actions, view }: { code: string; lang: s
59
61
  if (copying.current) {
60
62
  return;
61
63
  }
64
+
62
65
  copying.current = true;
63
66
  setCopyState('copying');
67
+
64
68
  try {
65
69
  // The best-effort clipboard helper cannot distinguish a refusal from success.
66
70
  await navigator.clipboard.writeText(code);
@@ -76,16 +80,20 @@ export function CodeBlock({ code, lang, actions, view }: { code: string; lang: s
76
80
  if (tokenizer !== null) {
77
81
  return;
78
82
  }
83
+
79
84
  let cancelled = false;
85
+
80
86
  Promise.all([highlighter.loadLanguage(lang), highlighter.loadTheme(theme, custom)])
81
87
  .then(() => !cancelled && setLoaded(`${lang}:${theme}`))
82
88
  .catch(() => !cancelled && setLoaded('failed'));
89
+
83
90
  return () => {
84
91
  cancelled = true;
85
92
  };
86
93
  }, [highlighter, lang, theme, custom, tokenizer]);
87
94
 
88
95
  const pending = (tokenizer === null || tokens === null) && loaded !== 'failed';
96
+
89
97
  return (
90
98
  <div className="chat-code" data-copy-state={copyState}>
91
99
  {view ?? (
@@ -70,9 +70,11 @@ function sendLabelKey(busy: boolean, steerable: boolean, delivery: 'queue' | 'st
70
70
  if (!busy) {
71
71
  return 'composer.send';
72
72
  }
73
+
73
74
  if (!steerable) {
74
75
  return 'composer.queueButton';
75
76
  }
77
+
76
78
  return delivery === 'steer' ? 'composer.steerButton' : 'composer.queueWithSteerButton';
77
79
  }
78
80
 
@@ -96,6 +98,7 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
96
98
  const notice = useNotice();
97
99
  const history = usePromptHistory(chatId, box.setText);
98
100
  const menus = useComposerMenus({ chatId, info, capabilities, providerName: provider?.name, draft: box, inputRef });
101
+
99
102
  const files = useDraftFiles({
100
103
  capabilities,
101
104
  providerName: provider?.name,
@@ -107,7 +110,9 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
107
110
  referables: menus.referables.chatsOnly ? menus.referables.items.filter((item) => item.kind === 'chat') : menus.referables.items,
108
111
  referenceChips: menus.referenceChips
109
112
  });
113
+
110
114
  const controls = useChatControls({ chatId, info, notice, onRetarget });
115
+
111
116
  useEffortStepper({
112
117
  chatId,
113
118
  info,
@@ -116,12 +121,14 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
116
121
  controls,
117
122
  setNotice: notice.setNotice
118
123
  });
124
+
119
125
  const guard = promptGuard(withQuote(draft.quote, draft.text));
120
126
  const continuable = useContinuableTurn(chatId, info);
121
127
  // The stopped turn something went out for, since the chat takes a moment to answer and Continue must not stand there meanwhile.
122
128
  const [sentFor, setSentFor] = useState<string | null>(null);
123
129
  const extraPrompts = useHostExtraPrompts(scope.id, chatId);
124
130
  const sendActions = useHostComposerSendActions(scope.id, chatId);
131
+
125
132
  const { submit, continueTurn, restoreStashed, toggleStash } = useComposerSubmit({
126
133
  chatId,
127
134
  writable,
@@ -138,6 +145,7 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
138
145
  onSend: (text, extras) => {
139
146
  setSentFor(continuable);
140
147
  const sent = onSend(text, extras);
148
+
141
149
  // A send the host did not take leaves Continue where it was; the original promise still reaches its caller.
142
150
  void Promise.resolve(sent).then(
143
151
  (taken) => {
@@ -147,12 +155,15 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
147
155
  },
148
156
  (error: unknown) => {
149
157
  setSentFor(null);
158
+
150
159
  throw error;
151
160
  }
152
161
  );
162
+
153
163
  return sent;
154
164
  }
155
165
  });
166
+
156
167
  const { takingBack, takeBack } = useTakeBack({
157
168
  chatId,
158
169
  draft: box,
@@ -162,16 +173,19 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
162
173
  inputRef.current?.focus();
163
174
  }
164
175
  });
176
+
165
177
  const pending = usePendingRequests(chatId);
166
178
  const compactionOffer = useCompactionOffer(chatId, info, capabilities?.compaction);
167
179
  const placeholder = useHintedPlaceholder({ chatId, disabled, readOnly, mentionable: capabilities?.mentions !== false, hasSkills: menus.hasSkills });
168
180
  const editorExtensions = [chipDecorations({ mentions: draft.mentions, skills: draft.skills, references: menus.referenceChips }), LEAVE_DROPS_TO_THE_CARD];
181
+
169
182
  /* The timeline owns the scroller; this is the one bit of it the composer needs to know. */
170
183
  const atEnd = useSyncExternalStore(
171
184
  subscribeTimelineEnd,
172
185
  () => timelineAtEnd(chatId),
173
186
  () => true
174
187
  );
188
+
175
189
  const queue = info.queue ?? [];
176
190
  const empty = isEmptyDraft(draft);
177
191
 
@@ -201,6 +215,7 @@ export function Composer({ chatId, info, focused, answerPromptsElsewhere, disabl
201
215
  if (!writable || event.button !== 0 || event.defaultPrevented || surfacePartOf(event.target, event.currentTarget) !== 'surface') {
202
216
  return;
203
217
  }
218
+
204
219
  // Nothing around the field selects, so the default would only blur it on the way, closing its picker and lifting a held prompt card.
205
220
  event.preventDefault();
206
221
  inputRef.current?.setCaret(Infinity);
@@ -69,12 +69,14 @@ export function ComposerInput({
69
69
  const hostRef = useRef<HTMLDivElement>(null);
70
70
  const targetRef = useRef<HTMLDivElement>(null);
71
71
  const viewRef = useRef<EditorView | null>(null);
72
+
72
73
  const [compartments] = useState(() => ({
73
74
  attributes: new Compartment(),
74
75
  editable: new Compartment(),
75
76
  placeholder: new Compartment(),
76
77
  extensions: new Compartment()
77
78
  }));
79
+
78
80
  const latest = useRef({ value, handlers });
79
81
  // What the editor last reported. The parent handing that back is catching up, not setting a value of its own.
80
82
  const reported = useRef(value);
@@ -85,10 +87,13 @@ export function ComposerInput({
85
87
 
86
88
  useLayoutEffect(() => {
87
89
  const host = hostRef.current;
90
+
88
91
  if (!host) {
89
92
  return;
90
93
  }
94
+
91
95
  const initial = latest.current.value;
96
+
92
97
  const view = new EditorView({
93
98
  parent: host,
94
99
  state: EditorState.create({
@@ -109,24 +114,30 @@ export function ComposerInput({
109
114
  cut: (event, target) => latest.current.handlers.onCopy?.(event, target) ?? false,
110
115
  compositionstart: () => {
111
116
  latest.current.handlers.onComposing?.(true);
117
+
112
118
  return false;
113
119
  },
114
120
  compositionend: () => {
115
121
  latest.current.handlers.onComposing?.(false);
122
+
116
123
  return false;
117
124
  }
118
125
  })
119
126
  ),
120
127
  EditorView.updateListener.of((update) => {
121
128
  const current = latest.current.handlers;
129
+
122
130
  if (update.focusChanged && !update.view.hasFocus) {
123
131
  current.onBlur?.();
124
132
  }
133
+
125
134
  if ((!update.docChanged && !update.selectionSet) || update.transactions.some((tr) => tr.annotation(externalChange))) {
126
135
  return;
127
136
  }
137
+
128
138
  const text = update.state.doc.toString();
129
139
  const { from, to } = update.state.selection.main;
140
+
130
141
  if (update.docChanged) {
131
142
  reported.current = text;
132
143
  current.onChange(text, { from, to }, update.state);
@@ -137,7 +148,9 @@ export function ComposerInput({
137
148
  ]
138
149
  })
139
150
  });
151
+
140
152
  viewRef.current = view;
153
+
141
154
  return () => {
142
155
  view.destroy();
143
156
  viewRef.current = null;
@@ -158,14 +171,18 @@ export function ComposerInput({
158
171
 
159
172
  useLayoutEffect(() => {
160
173
  const view = viewRef.current;
174
+
161
175
  if (!view || value === reported.current) {
162
176
  return;
163
177
  }
178
+
164
179
  reported.current = value;
165
180
  const doc = view.state.doc.toString();
181
+
166
182
  if (value === doc) {
167
183
  return;
168
184
  }
185
+
169
186
  view.dispatch({
170
187
  changes: { from: 0, to: doc.length, insert: value },
171
188
  selection: { anchor: value.length },
@@ -179,17 +196,21 @@ export function ComposerInput({
179
196
  focus: () => viewRef.current?.focus(),
180
197
  setCaret: (pos: number) => {
181
198
  const view = viewRef.current;
199
+
182
200
  if (!view) {
183
201
  return;
184
202
  }
203
+
185
204
  view.dispatch({ selection: { anchor: Math.min(pos, view.state.doc.length) }, scrollIntoView: true });
186
205
  view.focus();
187
206
  },
188
207
  insert: (text: string) => {
189
208
  const view = viewRef.current;
209
+
190
210
  if (!view) {
191
211
  return;
192
212
  }
213
+
193
214
  view.dispatch(insertAtSelection(view.state, text));
194
215
  view.focus();
195
216
  }
@@ -198,6 +219,7 @@ export function ComposerInput({
198
219
  );
199
220
 
200
221
  const Dictation = dictation.composer?.Control;
222
+
201
223
  return (
202
224
  <div ref={targetRef} className="min-w-0">
203
225
  <div ref={hostRef} className={className} />
@@ -9,9 +9,11 @@ const POOL_SIZE = 2;
9
9
  function ThemeSync() {
10
10
  const pool = useWorkerPool();
11
11
  const theme = useDiffTheme();
12
+
12
13
  useEffect(() => {
13
14
  void pool?.setRenderOptions({ theme });
14
15
  }, [pool, theme]);
16
+
15
17
  return null;
16
18
  }
17
19
 
@@ -21,6 +23,7 @@ function ThemeSync() {
21
23
  */
22
24
  export default function DiffPool({ workerFactory, children }: { workerFactory: () => Worker; children: ReactNode }) {
23
25
  const theme = useDiffTheme();
26
+
24
27
  return (
25
28
  <WorkerPoolContextProvider
26
29
  poolOptions={{
@@ -20,6 +20,7 @@ export default function EditDiff({ change, allowEditor = true, maxHeight }: { ch
20
20
  const legacyMode = !allowEditor || loadEngine === undefined;
21
21
  const legacyAllowed = legacyMode && legacyDiffBudget(change.before, change.after);
22
22
  const omitted = <DiffOmitted path={change.path} />;
23
+
23
24
  const fallback = legacyAllowed ? (
24
25
  <DiffFallbackBoundary fallback={omitted} resetKeys={[change.path, change.before, change.after]}>
25
26
  <LazyBoundary fallback={<div className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</div>}>
@@ -29,13 +30,17 @@ export default function EditDiff({ change, allowEditor = true, maxHeight }: { ch
29
30
  ) : (
30
31
  omitted
31
32
  );
33
+
32
34
  if (!sourceAllowed) {
33
35
  return omitted;
34
36
  }
37
+
35
38
  if (legacyMode) {
36
39
  return fallback;
37
40
  }
41
+
38
42
  const unavailable = <DiffUnavailable path={change.path} />;
43
+
39
44
  return (
40
45
  <DiffFallbackBoundary fallback={unavailable} resetKeys={[change.path, change.before, change.after, loadEngine]}>
41
46
  <LazyBoundary fallback={<div className="px-3 py-2 text-xs text-text-faint">{t('work.loadingDiff')}</div>}>
@@ -11,13 +11,16 @@ import { useChatScope } from '../../scope';
11
11
  import { useChats } from '../../state/chats';
12
12
  import { useChatActions } from '../actions';
13
13
  import { goalView } from '../logic/goal';
14
+ import { errorMessage } from '../../transport';
14
15
 
15
16
  /* How far the goal got, in what its CLI counts: the checks that sent the agent on, or the tokens it spent. */
16
17
  function progressOf(goal: ChatGoal, t: TFunction<'agent-chat'>): string | null {
17
18
  const parts: string[] = [];
19
+
18
20
  if (goal.checks !== undefined && goal.checks > 0) {
19
21
  parts.push(t('goal.checks', { count: goal.checks }));
20
22
  }
23
+
21
24
  if (goal.tokensUsed !== undefined && (goal.tokensUsed > 0 || goal.tokenBudget !== undefined)) {
22
25
  parts.push(
23
26
  goal.tokenBudget === undefined
@@ -25,6 +28,7 @@ function progressOf(goal: ChatGoal, t: TFunction<'agent-chat'>): string | null {
25
28
  : t('goal.budget', { tokens: formatTokens(goal.tokensUsed), budget: formatTokens(goal.tokenBudget) })
26
29
  );
27
30
  }
31
+
28
32
  return parts.length === 0 ? null : parts.join(' · ');
29
33
  }
30
34
 
@@ -39,21 +43,27 @@ export function GoalDock({ chatId, info, goals }: { chatId: string; info: ChatIn
39
43
  const actions = useChatActions();
40
44
  const [changing, setChanging] = useState(false);
41
45
  const view = goalView(info, goals, actions.goal !== undefined);
46
+
42
47
  if (view === null) {
43
48
  return null;
44
49
  }
50
+
45
51
  const { goal } = view;
46
52
  const progress = progressOf(goal, t);
53
+
47
54
  const act = (action: ChatGoalAction): void => {
48
55
  if (!actions.goal) {
49
56
  return;
50
57
  }
58
+
51
59
  setChanging(true);
60
+
52
61
  actions
53
62
  .goal(chatId, action)
54
- .catch((e: unknown) => host.notify({ kind: 'error', title: t('goal.failed'), description: e instanceof Error ? e.message : String(e) }))
63
+ .catch((e: unknown) => host.notify({ kind: 'error', title: t('goal.failed'), description: errorMessage(e) }))
55
64
  .finally(() => setChanging(false));
56
65
  };
66
+
57
67
  return (
58
68
  <div className="flex items-center gap-2 border-b border-border px-3.5 py-2.5">
59
69
  <Icon icon={Target} size={16} className={clsx('shrink-0', goal.status === 'active' ? 'text-accent' : 'text-text-faint')} />
@@ -91,9 +101,11 @@ export function GoalPill({ chatId }: { chatId: string }) {
91
101
  const { t } = useTranslation('agent-chat');
92
102
  const { keyOf } = useChatScope();
93
103
  const goal = useChats((s) => s.statusByKey[keyOf(chatId)]?.info.goal);
104
+
94
105
  if (goal?.status !== 'active') {
95
106
  return null;
96
107
  }
108
+
97
109
  return (
98
110
  <Tooltip label={`${t('goal.status.active')}: ${goal.objective}`}>
99
111
  <Pill tone="accent" icon={<Icon icon={Target} size={12} />}>
@@ -26,10 +26,13 @@ const START: View = { scale: 1, x: 0, y: 0 };
26
26
  */
27
27
  function zoomed(view: View, factor: number, pointX: number, pointY: number): View {
28
28
  const scale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, view.scale * factor));
29
+
29
30
  if (scale === MIN_SCALE) {
30
31
  return START;
31
32
  }
33
+
32
34
  const ratio = scale / view.scale;
35
+
33
36
  return { scale, x: pointX - (pointX - view.x) * ratio, y: pointY - (pointY - view.y) * ratio };
34
37
  }
35
38
 
@@ -62,9 +65,11 @@ function Lightbox({
62
65
  /* Where the pointer sits against the middle of the frame, which is what the transform counts from. */
63
66
  const pointIn = (event: { clientX: number; clientY: number }): { x: number; y: number } => {
64
67
  const rect = frameRef.current?.getBoundingClientRect();
68
+
65
69
  if (!rect) {
66
70
  return { x: 0, y: 0 };
67
71
  }
72
+
68
73
  return { x: event.clientX - rect.left - rect.width / 2, y: event.clientY - rect.top - rect.height / 2 };
69
74
  };
70
75
 
@@ -114,11 +119,13 @@ function Lightbox({
114
119
  if (view.scale <= MIN_SCALE) {
115
120
  return;
116
121
  }
122
+
117
123
  e.currentTarget.setPointerCapture(e.pointerId);
118
124
  dragRef.current = { pointerId: e.pointerId, x: e.clientX - view.x, y: e.clientY - view.y };
119
125
  }}
120
126
  onPointerMove={(e) => {
121
127
  const drag = dragRef.current;
128
+
122
129
  if (drag?.pointerId === e.pointerId) {
123
130
  setView((current) => ({ ...current, x: e.clientX - drag.x, y: e.clientY - drag.y }));
124
131
  }
@@ -163,12 +170,15 @@ export interface ImageThumbProps {
163
170
  export function ImageThumb({ source, alt, className, aspect, actions, open: openProp, onOpenChange }: ImageThumbProps) {
164
171
  const [ownOpen, setOwnOpen] = useState(false);
165
172
  const open = openProp ?? ownOpen;
173
+
166
174
  const setOpen = (next: boolean): void => {
167
175
  setOwnOpen(next);
168
176
  onOpenChange?.(next);
169
177
  };
178
+
170
179
  const { url: src, failure } = source;
171
180
  const ratio = aspect === undefined ? undefined : { aspectRatio: aspect };
181
+
172
182
  if (src === null) {
173
183
  return (
174
184
  <span
@@ -186,6 +196,7 @@ export function ImageThumb({ source, alt, className, aspect, actions, open: open
186
196
  </span>
187
197
  );
188
198
  }
199
+
189
200
  return (
190
201
  <>
191
202
  <button
@@ -18,6 +18,7 @@ import { useChatActions } from '../actions';
18
18
  import { useUsageStore } from '../../state/usage';
19
19
  import { SessionLine } from './SessionLine';
20
20
  import { useHostResumeAtReset } from '../../host-hooks';
21
+ import { errorMessage } from '../../transport';
21
22
 
22
23
  // A time a limit names reads as a clock today and with its day after that, so the words follow the minute.
23
24
  const MINUTE_MS = 60_000;
@@ -32,10 +33,13 @@ const refreshedAt = new Map<string, number>();
32
33
  */
33
34
  function readUnreadAccounts(scope: ChatScope): void {
34
35
  const last = refreshedAt.get(scope.id);
36
+
35
37
  if (last !== undefined && Date.now() - last < UNREAD_REFRESH_MS) {
36
38
  return;
37
39
  }
40
+
38
41
  refreshedAt.set(scope.id, Date.now());
42
+
39
43
  void scope.transport
40
44
  .request('usage.refreshLimits', {})
41
45
  .then((snapshot) => useUsageStore.getState().setLimits(scope.id, snapshot))
@@ -51,9 +55,11 @@ function ResumeAtResetToggle({ chatId, info }: { chatId: string; info: ChatInfo
51
55
  const { id } = useChatScope();
52
56
  const actions = useChatActions();
53
57
  const machineAllows = useHostResumeAtReset(id);
58
+
54
59
  const set = (on: boolean): void => {
55
60
  void actions.configure(chatId, { resumeAtReset: on }).catch(() => undefined);
56
61
  };
62
+
57
63
  return (
58
64
  <Tooltip label={machineAllows ? t('limit.toggle') : t('limit.machineOff')}>
59
65
  {/* A disabled switch does not take the pointer, so the tooltip hangs on a wrapper. */}
@@ -65,11 +71,8 @@ function ResumeAtResetToggle({ chatId, info }: { chatId: string; info: ChatInfo
65
71
  }
66
72
 
67
73
  /*
68
- * Another account of the chat's CLI that has room: the one with the least of its session spent, and
69
- * beside it a menu of every account with room, for a person whose companies each pay for their own
70
- * work. Only ever offered, since going on there spends someone's other plan, and nothing is
71
- * remembered: the next limit suggests the one with the most room again. An account that reads the
72
- * chat's conversation takes it over; any other goes on in a fork, which then opens the way a fork does.
74
+ * Another account of the chat's CLI with room, only ever offered since it spends someone's other plan, and
75
+ * never remembered. One that reads the chat's conversation takes it over; any other goes on in a fork.
73
76
  */
74
77
  function ContinueOn({ chatId, info, choice }: { chatId: string; info: ChatInfo; choice: AccountChoice }) {
75
78
  const host = useChatHost();
@@ -88,24 +91,29 @@ function ContinueOn({ chatId, info, choice }: { chatId: string; info: ChatInfo;
88
91
  }, [scope, unread]);
89
92
 
90
93
  const target = targets[0];
94
+
91
95
  if (target === undefined) {
92
96
  return null;
93
97
  }
98
+
94
99
  const go = (entry: AccountEntry): void => {
95
100
  setBusy(true);
101
+
96
102
  actions
97
103
  .continueOn(chatId, entry.id)
98
104
  .catch((e: unknown) =>
99
105
  host.notify({
100
106
  kind: 'error',
101
107
  title: t('limit.continueFailed', { account: choice.nameOf(entry) }),
102
- description: e instanceof Error ? e.message : String(e)
108
+ description: errorMessage(e)
103
109
  })
104
110
  )
105
111
  .finally(() => setBusy(false));
106
112
  };
113
+
107
114
  const name = choice.nameOf(target);
108
115
  const split = targets.length > 1;
116
+
109
117
  const suggestion = (
110
118
  <Tooltip label={t('limit.continueOnTip', { account: name })}>
111
119
  <Button
@@ -122,9 +130,11 @@ function ContinueOn({ chatId, info, choice }: { chatId: string; info: ChatInfo;
122
130
  </Button>
123
131
  </Tooltip>
124
132
  );
133
+
125
134
  if (!split) {
126
135
  return suggestion;
127
136
  }
137
+
128
138
  return (
129
139
  <ButtonGroup className="shrink-0">
130
140
  {suggestion}
@@ -160,10 +170,12 @@ function ContinueOnRows({
160
170
  const { t } = useTranslation('agent-chat');
161
171
  const limits = useUsageLimits();
162
172
  const now = useNow(MINUTE_MS);
173
+
163
174
  return targets.map((entry) => {
164
175
  const name = choice.nameOf(entry);
165
176
  const session = sessionWindow(limitsOfAccount(limits, entry.id));
166
177
  const fork = !canContinueOn(choice.accounts, info.provider, info.account, entry.id);
178
+
167
179
  return (
168
180
  <Menu.Item
169
181
  key={entry.id}
@@ -191,12 +203,15 @@ export function LimitDock({ chatId, info }: { chatId: string; info: ChatInfo })
191
203
  const now = useNow(MINUTE_MS, info.limit !== undefined);
192
204
  const choice = useAccountChoice(info.provider, info.account);
193
205
  const view = limitView(info, now);
206
+
194
207
  if (view === null) {
195
208
  return null;
196
209
  }
210
+
197
211
  // With a choice of accounts the line names the one that ran into the limit.
198
212
  const account = choice === null ? null : choice.current === null ? choice.currentId : choice.nameOf(choice.current);
199
213
  const detail = account === null ? view.detail : view.detail === null ? account : `${account} · ${view.detail}`;
214
+
200
215
  return (
201
216
  <div className="flex items-center gap-2 border-b border-border px-3.5 py-2.5">
202
217
  <Icon icon={Hourglass} size={16} className="shrink-0 text-text-faint" />
@@ -218,9 +233,11 @@ export function LimitPill({ chatId }: { chatId: string }) {
218
233
  useFormatLocale();
219
234
  const now = useNow(MINUTE_MS, info?.limit !== undefined);
220
235
  const view = info === undefined ? null : limitView(info, now);
236
+
221
237
  if (info === undefined || view === null) {
222
238
  return null;
223
239
  }
240
+
224
241
  return (
225
242
  <>
226
243
  <Tooltip label={view.detail === null ? view.title : `${view.title}. ${view.detail}`}>