@adecore/agents-react 0.38.0 → 0.40.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 (351) hide show
  1. package/dist/agents/AgentModelLabel.d.ts +10 -0
  2. package/dist/agents/AgentModelLabel.js +98 -0
  3. package/dist/agents/model-name.js +2 -1
  4. package/dist/agents/provider-paths.d.ts +2 -1
  5. package/dist/agents/provider-paths.js +7 -3
  6. package/dist/chat/account-choice.js +57 -28
  7. package/dist/chat/bookmarks.js +2 -1
  8. package/dist/chat/drafts.d.ts +0 -1
  9. package/dist/chat/drafts.js +9 -28
  10. package/dist/chat/logic/picker-entries.js +2 -8
  11. package/dist/chat/logic/resume-compaction.js +2 -5
  12. package/dist/chat/logic/timeline-copy.js +2 -8
  13. package/dist/chat/logic/timeline.js +9 -10
  14. package/dist/chat/logic/workflow.js +2 -4
  15. package/dist/chat/subagent-conversation.js +2 -1
  16. package/dist/chat/ui/Composer.js +385 -360
  17. package/dist/chat/ui/GoalDock.js +2 -1
  18. package/dist/chat/ui/LimitState.js +4 -6
  19. package/dist/chat/ui/Pickers.d.ts +2 -1
  20. package/dist/chat/ui/Pickers.js +126 -123
  21. package/dist/chat/ui/PromptEditor.d.ts +19 -0
  22. package/dist/chat/ui/PromptEditor.js +115 -0
  23. package/dist/chat/ui/Timeline.d.ts +2 -1
  24. package/dist/chat/ui/Timeline.js +13 -1
  25. package/dist/chat/ui/composer/ComposerControls.d.ts +3 -0
  26. package/dist/chat/ui/composer/ComposerControls.js +235 -38
  27. package/dist/chat/ui/composer/key-handler.d.ts +1 -1
  28. package/dist/chat/ui/composer/key-handler.js +3 -11
  29. package/dist/chat/ui/composer/prompt-picks.d.ts +4 -0
  30. package/dist/chat/ui/composer/prompt-picks.js +14 -0
  31. package/dist/chat/ui/composer/use-composer-menus.d.ts +4 -49
  32. package/dist/chat/ui/composer/use-composer-menus.js +65 -519
  33. package/dist/chat/ui/composer/use-continuable-turn.js +0 -1
  34. package/dist/chat/ui/composer/use-notice.js +2 -1
  35. package/dist/chat/ui/composer/use-prompt-history.js +1 -1
  36. package/dist/chat/ui/composer/use-prompt-menus.d.ts +73 -0
  37. package/dist/chat/ui/composer/use-prompt-menus.js +499 -0
  38. package/dist/chat/ui/composer/use-take-back.js +2 -2
  39. package/dist/chat/ui/diff/ApprovalReview.js +1 -1
  40. package/dist/chat/ui/highlighter.d.ts +0 -6
  41. package/dist/chat/ui/highlighter.js +0 -12
  42. package/dist/chat/ui/icons.d.ts +0 -1
  43. package/dist/chat/ui/icons.js +0 -3
  44. package/dist/chat/ui/index.d.ts +3 -0
  45. package/dist/chat/ui/index.js +2 -0
  46. package/dist/chat/ui/rows/MessageRows.js +2 -5
  47. package/dist/chat/ui/rows/row-rhythm.js +2 -5
  48. package/dist/chat/ui/theme-watch.js +3 -7
  49. package/dist/host-hooks.d.ts +2 -1
  50. package/dist/host-hooks.js +6 -0
  51. package/dist/host.d.ts +17 -1
  52. package/dist/index.d.ts +1 -1
  53. package/dist/locales/en/agent-chat.d.ts +1 -0
  54. package/dist/locales/en/agent-chat.js +1 -0
  55. package/dist/locales/nl/agent-chat.d.ts +1 -0
  56. package/dist/locales/nl/agent-chat.js +1 -0
  57. package/dist/prompts/logic/usePromptSession.js +2 -1
  58. package/dist/providers/AddAccountForm.js +2 -1
  59. package/dist/providers/CliDetail.js +104 -130
  60. package/dist/providers/account-actions.js +6 -9
  61. package/dist/providers/index.d.ts +1 -0
  62. package/dist/providers/index.js +1 -0
  63. package/dist/state/chats.d.ts +1 -0
  64. package/dist/state/chats.js +6 -11
  65. package/dist/state/provider-accounts.d.ts +1 -1
  66. package/dist/state/provider-accounts.js +2 -8
  67. package/dist/theme.css +3 -0
  68. package/dist/transport.d.ts +2 -0
  69. package/dist/transport.js +14 -0
  70. package/dist/usage/ChartLogo.d.ts +5 -0
  71. package/dist/usage/ChartLogo.js +48 -0
  72. package/dist/usage/LimitsList.js +6 -16
  73. package/dist/usage/UsageBreakdown.js +17 -40
  74. package/dist/usage/UsageLimits.js +183 -211
  75. package/dist/usage/UsagePage.js +3 -17
  76. package/dist/usage/UsageSummary.js +4 -9
  77. package/dist/usage/limits.js +16 -31
  78. package/dist/usage/summary.d.ts +1 -1
  79. package/dist/usage/summary.js +3 -5
  80. package/package.json +26 -26
  81. package/src/agents/AccountDot.tsx +1 -0
  82. package/src/agents/AgentIcon.tsx +2 -0
  83. package/src/agents/AgentModelLabel.tsx +38 -0
  84. package/src/agents/ModelOptionControl.tsx +8 -0
  85. package/src/agents/account-limits.ts +3 -0
  86. package/src/agents/accounts.ts +12 -0
  87. package/src/agents/model-name.ts +13 -1
  88. package/src/agents/model-options.ts +2 -0
  89. package/src/agents/model-requirement.ts +4 -0
  90. package/src/agents/option-words.ts +2 -0
  91. package/src/agents/provider-paths.ts +8 -3
  92. package/src/agents/status-look.ts +1 -0
  93. package/src/chat/account-choice.ts +25 -19
  94. package/src/chat/actions.ts +5 -0
  95. package/src/chat/attachments.ts +8 -0
  96. package/src/chat/bookmarks.ts +13 -1
  97. package/src/chat/chat-client.ts +63 -0
  98. package/src/chat/chat-references.ts +3 -0
  99. package/src/chat/composer-effort.ts +5 -0
  100. package/src/chat/composer-send.ts +4 -0
  101. package/src/chat/drafts.ts +21 -17
  102. package/src/chat/forks.ts +4 -0
  103. package/src/chat/guards.ts +2 -0
  104. package/src/chat/index.ts +7 -0
  105. package/src/chat/logic/ansi.ts +47 -0
  106. package/src/chat/logic/bookmarks.ts +7 -0
  107. package/src/chat/logic/command-looks.ts +29 -0
  108. package/src/chat/logic/context-usage.ts +3 -0
  109. package/src/chat/logic/continue-after-stop.ts +5 -0
  110. package/src/chat/logic/effort.ts +4 -0
  111. package/src/chat/logic/embedded-code.ts +63 -0
  112. package/src/chat/logic/fork.ts +22 -0
  113. package/src/chat/logic/generated-image.ts +7 -0
  114. package/src/chat/logic/goal.ts +7 -0
  115. package/src/chat/logic/handback.ts +2 -0
  116. package/src/chat/logic/limit.ts +8 -0
  117. package/src/chat/logic/picker-entries.ts +16 -4
  118. package/src/chat/logic/resume-compaction.ts +22 -5
  119. package/src/chat/logic/scrubber.ts +38 -5
  120. package/src/chat/logic/thought.ts +5 -0
  121. package/src/chat/logic/thread-cards.ts +6 -0
  122. package/src/chat/logic/timeline-copy.ts +4 -9
  123. package/src/chat/logic/timeline-target.ts +15 -0
  124. package/src/chat/logic/timeline.ts +113 -10
  125. package/src/chat/logic/tools.ts +30 -0
  126. package/src/chat/logic/visual-height.ts +5 -0
  127. package/src/chat/logic/visual-viewport.ts +1 -0
  128. package/src/chat/logic/welcome.ts +3 -0
  129. package/src/chat/logic/workflow.ts +26 -7
  130. package/src/chat/mentions.ts +56 -0
  131. package/src/chat/pending-prompts.ts +16 -0
  132. package/src/chat/persisted-json.ts +15 -0
  133. package/src/chat/preferences.ts +16 -0
  134. package/src/chat/prompt-hold.ts +2 -0
  135. package/src/chat/quote.ts +7 -0
  136. package/src/chat/reading-width.ts +1 -0
  137. package/src/chat/recent-messages.ts +5 -0
  138. package/src/chat/resume-compaction-dismissals.ts +1 -0
  139. package/src/chat/stash.ts +12 -0
  140. package/src/chat/subagent-conversation.ts +24 -4
  141. package/src/chat/subagent-list.ts +14 -0
  142. package/src/chat/subagent-support.ts +1 -0
  143. package/src/chat/subagent-view.ts +9 -0
  144. package/src/chat/thumbnails.ts +14 -0
  145. package/src/chat/timeline-flash.ts +2 -0
  146. package/src/chat/timeline-scroll.ts +23 -0
  147. package/src/chat/ui/AccountPill.tsx +4 -0
  148. package/src/chat/ui/AnsiOutput.tsx +1 -0
  149. package/src/chat/ui/BookmarkMarker.tsx +5 -0
  150. package/src/chat/ui/BookmarkSubmenu.tsx +1 -0
  151. package/src/chat/ui/ChatActivity.tsx +13 -0
  152. package/src/chat/ui/CodeBlock.tsx +8 -0
  153. package/src/chat/ui/Composer.tsx +27 -3
  154. package/src/chat/ui/ComposerInput.tsx +22 -0
  155. package/src/chat/ui/DiffPool.tsx +3 -0
  156. package/src/chat/ui/EditDiff.tsx +5 -0
  157. package/src/chat/ui/GoalDock.tsx +13 -1
  158. package/src/chat/ui/ImageView.tsx +11 -0
  159. package/src/chat/ui/LimitState.tsx +23 -6
  160. package/src/chat/ui/Markdown.tsx +41 -0
  161. package/src/chat/ui/MermaidBlock.tsx +16 -0
  162. package/src/chat/ui/MessageActions.tsx +8 -0
  163. package/src/chat/ui/ModelMarks.tsx +1 -0
  164. package/src/chat/ui/Pickers.tsx +30 -1
  165. package/src/chat/ui/PromptComposer.tsx +2 -0
  166. package/src/chat/ui/PromptEditor.tsx +136 -0
  167. package/src/chat/ui/QuoteButton.tsx +12 -0
  168. package/src/chat/ui/ReferenceChip.tsx +3 -0
  169. package/src/chat/ui/ResumeCompactionDock.tsx +1 -0
  170. package/src/chat/ui/RowTime.tsx +1 -0
  171. package/src/chat/ui/RunSettings.tsx +38 -0
  172. package/src/chat/ui/Scrubber.tsx +14 -0
  173. package/src/chat/ui/SessionLine.tsx +1 -0
  174. package/src/chat/ui/ShellCommand.tsx +32 -0
  175. package/src/chat/ui/SubagentControls.tsx +7 -0
  176. package/src/chat/ui/SubagentInfo.tsx +1 -0
  177. package/src/chat/ui/SubagentStopButton.tsx +6 -0
  178. package/src/chat/ui/SubagentTimeline.tsx +10 -0
  179. package/src/chat/ui/Timeline.tsx +141 -1
  180. package/src/chat/ui/TimelineMenu.tsx +5 -0
  181. package/src/chat/ui/UnifiedDiff.tsx +6 -0
  182. package/src/chat/ui/UploadThumb.tsx +2 -0
  183. package/src/chat/ui/VisualDialogs.tsx +3 -0
  184. package/src/chat/ui/VisualFrame.tsx +15 -0
  185. package/src/chat/ui/Welcome.tsx +1 -0
  186. package/src/chat/ui/chip-clipboard.ts +20 -0
  187. package/src/chat/ui/code-lines.ts +14 -0
  188. package/src/chat/ui/code-theme.ts +1 -0
  189. package/src/chat/ui/composer/ComposerControls.tsx +70 -15
  190. package/src/chat/ui/composer/ComposerMenu.tsx +12 -0
  191. package/src/chat/ui/composer/DraftParts.tsx +2 -0
  192. package/src/chat/ui/composer/QueuedMessages.tsx +4 -0
  193. package/src/chat/ui/composer/chip-paste.ts +16 -0
  194. package/src/chat/ui/composer/chips.ts +6 -0
  195. package/src/chat/ui/composer/editor.ts +9 -0
  196. package/src/chat/ui/composer/insert.ts +2 -0
  197. package/src/chat/ui/composer/key-handler.ts +42 -9
  198. package/src/chat/ui/composer/keys.ts +23 -0
  199. package/src/chat/ui/composer/placeholder.ts +6 -0
  200. package/src/chat/ui/composer/prompt-picks.ts +18 -0
  201. package/src/chat/ui/composer/surface.ts +5 -0
  202. package/src/chat/ui/composer/use-chat-controls.ts +13 -0
  203. package/src/chat/ui/composer/use-compaction-offer.ts +3 -0
  204. package/src/chat/ui/composer/use-composer-menus.ts +20 -275
  205. package/src/chat/ui/composer/use-composer-submit.ts +27 -0
  206. package/src/chat/ui/composer/use-continuable-turn.ts +3 -5
  207. package/src/chat/ui/composer/use-draft-files.ts +24 -0
  208. package/src/chat/ui/composer/use-effort-stepper.ts +8 -0
  209. package/src/chat/ui/composer/use-notice.ts +4 -1
  210. package/src/chat/ui/composer/use-pending-requests.ts +3 -0
  211. package/src/chat/ui/composer/use-prompt-history.ts +20 -11
  212. package/src/chat/ui/composer/use-prompt-hold.ts +3 -0
  213. package/src/chat/ui/composer/use-prompt-menus.ts +361 -0
  214. package/src/chat/ui/composer/use-take-back.ts +20 -2
  215. package/src/chat/ui/diff/ApprovalReview.tsx +18 -15
  216. package/src/chat/ui/diff/DiffFallbackBoundary.tsx +1 -0
  217. package/src/chat/ui/diff/DiffOmitted.tsx +1 -0
  218. package/src/chat/ui/diff/DiffStack.tsx +2 -0
  219. package/src/chat/ui/diff/DiffUnavailable.tsx +1 -0
  220. package/src/chat/ui/diff/EditorEditDiff.tsx +13 -0
  221. package/src/chat/ui/diff/EditorPatchDiff.tsx +4 -0
  222. package/src/chat/ui/diff/LegacyDiffPool.tsx +2 -0
  223. package/src/chat/ui/diff/LegacyEditDiff.tsx +2 -0
  224. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +8 -0
  225. package/src/chat/ui/diff/RawDiff.tsx +1 -0
  226. package/src/chat/ui/diff/ReviewDiff.tsx +1 -0
  227. package/src/chat/ui/diff/diff-source-budget.ts +8 -0
  228. package/src/chat/ui/diff/editor-edit-model.ts +4 -0
  229. package/src/chat/ui/diff/review-plan.ts +9 -0
  230. package/src/chat/ui/diff/review-settle.ts +1 -0
  231. package/src/chat/ui/diff/syntax-runtime.ts +52 -0
  232. package/src/chat/ui/diff/use-review-stack.ts +9 -0
  233. package/src/chat/ui/diff-syntax.ts +12 -0
  234. package/src/chat/ui/diff-theme.ts +2 -0
  235. package/src/chat/ui/file-links.ts +2 -0
  236. package/src/chat/ui/find-reveal.ts +1 -0
  237. package/src/chat/ui/full-diff.ts +1 -0
  238. package/src/chat/ui/highlighter.ts +29 -15
  239. package/src/chat/ui/icons.tsx +2 -4
  240. package/src/chat/ui/index.ts +7 -0
  241. package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +25 -0
  242. package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +1 -0
  243. package/src/chat/ui/intelligent-ui/UiReply.tsx +63 -0
  244. package/src/chat/ui/intelligent-ui/block-local.ts +5 -0
  245. package/src/chat/ui/intelligent-ui/chart-data.ts +15 -0
  246. package/src/chat/ui/intelligent-ui/node-text.ts +17 -0
  247. package/src/chat/ui/intelligent-ui/renderers/binding.ts +1 -0
  248. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +31 -0
  249. package/src/chat/ui/intelligent-ui/renderers/choices.tsx +4 -0
  250. package/src/chat/ui/intelligent-ui/renderers/content.tsx +18 -0
  251. package/src/chat/ui/intelligent-ui/renderers/data.tsx +11 -0
  252. package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +13 -0
  253. package/src/chat/ui/intelligent-ui/renderers/links.tsx +9 -0
  254. package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +15 -0
  255. package/src/chat/ui/intelligent-ui/renderers/map-style.ts +15 -0
  256. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +48 -0
  257. package/src/chat/ui/intelligent-ui/renderers/map.tsx +20 -0
  258. package/src/chat/ui/intelligent-ui/renderers/place-point.ts +3 -0
  259. package/src/chat/ui/intelligent-ui/renderers/structure.tsx +3 -0
  260. package/src/chat/ui/intelligent-ui/renderers/text.tsx +8 -0
  261. package/src/chat/ui/intelligent-ui/reply-parts.ts +5 -0
  262. package/src/chat/ui/intelligent-ui/table-data.ts +17 -0
  263. package/src/chat/ui/intelligent-ui/use-in-view.ts +4 -0
  264. package/src/chat/ui/intelligent-ui/use-later.ts +4 -0
  265. package/src/chat/ui/intelligent-ui/use-truncated.ts +5 -0
  266. package/src/chat/ui/intelligent-ui/use-ui-links.ts +29 -0
  267. package/src/chat/ui/intelligent-ui/use-ui-queries.ts +60 -0
  268. package/src/chat/ui/markdown-blocks.ts +64 -0
  269. package/src/chat/ui/mermaid.ts +4 -0
  270. package/src/chat/ui/quote-selection.ts +10 -0
  271. package/src/chat/ui/rehype-chips.ts +5 -0
  272. package/src/chat/ui/rehype-fade.ts +2 -0
  273. package/src/chat/ui/remark-html-as-text.ts +5 -0
  274. package/src/chat/ui/reveal.ts +18 -0
  275. package/src/chat/ui/rows/ForksRow.tsx +5 -0
  276. package/src/chat/ui/rows/GeneratedImageRow.tsx +10 -0
  277. package/src/chat/ui/rows/MessageRows.tsx +29 -5
  278. package/src/chat/ui/rows/RowContainer.tsx +1 -0
  279. package/src/chat/ui/rows/Rows.tsx +2 -0
  280. package/src/chat/ui/rows/SubagentRow.tsx +13 -0
  281. package/src/chat/ui/rows/TaskRow.tsx +7 -0
  282. package/src/chat/ui/rows/VisualRow.tsx +4 -0
  283. package/src/chat/ui/rows/WorkRows.tsx +46 -0
  284. package/src/chat/ui/rows/WorkflowRow.tsx +10 -0
  285. package/src/chat/ui/rows/row-rhythm.ts +7 -5
  286. package/src/chat/ui/theme-watch.ts +8 -7
  287. package/src/chat/ui/thread-references.ts +7 -0
  288. package/src/chat/ui/use-diff-source-tokens.ts +8 -0
  289. package/src/chat/ui/useToggleSet.ts +3 -0
  290. package/src/chat/ui/visual-theme.ts +30 -0
  291. package/src/chat/visual-bridge.ts +23 -0
  292. package/src/chat/visual-viewport.ts +12 -0
  293. package/src/host-hooks.ts +15 -5
  294. package/src/host.ts +29 -1
  295. package/src/index.ts +5 -0
  296. package/src/lazy.tsx +9 -0
  297. package/src/locales/en/agent-chat.ts +1 -0
  298. package/src/locales/nl/agent-chat.ts +1 -0
  299. package/src/mounted-registry.ts +4 -0
  300. package/src/port-transport.ts +32 -0
  301. package/src/prompts/index.ts +2 -0
  302. package/src/prompts/logic/focus.ts +5 -0
  303. package/src/prompts/logic/keys.ts +12 -0
  304. package/src/prompts/logic/prompts.ts +8 -0
  305. package/src/prompts/logic/subjects.ts +8 -0
  306. package/src/prompts/logic/useFocusAfterAnswer.ts +2 -0
  307. package/src/prompts/logic/usePromptSession.ts +13 -1
  308. package/src/prompts/ui/ApprovalBody.tsx +2 -0
  309. package/src/prompts/ui/PromptActions.tsx +2 -0
  310. package/src/prompts/ui/PromptCard.tsx +6 -0
  311. package/src/prompts/ui/PromptView.tsx +6 -0
  312. package/src/prompts/ui/QuestionBody.tsx +11 -0
  313. package/src/providers/AccountColors.tsx +1 -0
  314. package/src/providers/AccountDetail.tsx +7 -0
  315. package/src/providers/AccountVariables.tsx +2 -0
  316. package/src/providers/AddAccountForm.tsx +5 -1
  317. package/src/providers/CliDetail.tsx +6 -5
  318. package/src/providers/InstallPage.tsx +11 -0
  319. package/src/providers/ProviderList.tsx +3 -0
  320. package/src/providers/ProvidersPane.tsx +18 -0
  321. package/src/providers/account-actions.ts +10 -8
  322. package/src/providers/index.ts +1 -0
  323. package/src/providers/install-steps.ts +3 -0
  324. package/src/providers/pane-target.ts +5 -0
  325. package/src/providers/provider-abilities.ts +1 -0
  326. package/src/providers/provider-groups.ts +6 -0
  327. package/src/providers/variables.ts +12 -0
  328. package/src/scope.ts +2 -0
  329. package/src/state/chats.ts +61 -14
  330. package/src/state/index.ts +2 -0
  331. package/src/state/provider-accounts.ts +7 -9
  332. package/src/state/providers.ts +1 -0
  333. package/src/state/usage.ts +6 -0
  334. package/src/storage.ts +18 -0
  335. package/src/testing/prompts.ts +5 -0
  336. package/src/theme.css +3 -0
  337. package/src/transport.ts +18 -0
  338. package/src/usage/ChartLogo.tsx +12 -0
  339. package/src/usage/LimitsList.tsx +7 -8
  340. package/src/usage/UsageBreakdown.tsx +11 -4
  341. package/src/usage/UsageChart.tsx +5 -0
  342. package/src/usage/UsageLimits.tsx +10 -7
  343. package/src/usage/UsageLimitsCard.tsx +2 -0
  344. package/src/usage/UsagePage.tsx +16 -19
  345. package/src/usage/UsageSummary.tsx +4 -5
  346. package/src/usage/UsageTiles.tsx +2 -0
  347. package/src/usage/format.ts +7 -0
  348. package/src/usage/limit-groups.ts +14 -0
  349. package/src/usage/limits.ts +11 -13
  350. package/src/usage/money.ts +1 -0
  351. package/src/usage/summary.ts +15 -7
package/src/chat/quote.ts CHANGED
@@ -10,6 +10,7 @@ export function quoteOf(selected: string): string | null {
10
10
  .join('\n')
11
11
  .replace(/^\n+|\n+$/g, '')
12
12
  .replace(/\n{3,}/g, '\n\n');
13
+
13
14
  return text.trim() === '' ? null : text;
14
15
  }
15
16
 
@@ -18,10 +19,12 @@ export function withQuote(quote: string, message: string): string {
18
19
  if (quote === '') {
19
20
  return message;
20
21
  }
22
+
21
23
  const block = quote
22
24
  .split('\n')
23
25
  .map((line) => (line === '' ? '>' : `> ${line}`))
24
26
  .join('\n');
27
+
25
28
  return message === '' ? block : `${block}\n\n${message}`;
26
29
  }
27
30
 
@@ -32,15 +35,19 @@ export function withQuote(quote: string, message: string): string {
32
35
  export function splitQuote(text: string): { quote: string; rest: string } | null {
33
36
  const lines = text.split('\n');
34
37
  let end = 0;
38
+
35
39
  while (end < lines.length && (lines[end] === '>' || lines[end]!.startsWith('> '))) {
36
40
  end += 1;
37
41
  }
42
+
38
43
  if (end === 0 || (end < lines.length && lines[end] !== '')) {
39
44
  return null;
40
45
  }
46
+
41
47
  const quote = lines
42
48
  .slice(0, end)
43
49
  .map((line) => line.slice(2))
44
50
  .join('\n');
51
+
45
52
  return { quote, rest: lines.slice(end + 1).join('\n') };
46
53
  }
@@ -31,6 +31,7 @@ const WIDE_MEASURE = 1200;
31
31
  export function readingWidthStyle(width: ChatReadingWidth): CSSProperties {
32
32
  const measure = MEASURES[width];
33
33
  const bounded = Number.isFinite(measure);
34
+
34
35
  return {
35
36
  '--chat-measure': bounded ? `${measure}px` : '100%',
36
37
  '--chat-wide-measure': bounded ? `${WIDE_MEASURE}px` : '100%'
@@ -30,18 +30,23 @@ export function recentMessages(eligible: readonly MessageItem[], limit: number):
30
30
  const messages: RecentChatMessage[] = [];
31
31
  let characters = 0;
32
32
  let truncated = candidates.length < eligible.length;
33
+
33
34
  for (const item of candidates.toReversed()) {
34
35
  const message = clipped(item.text.trim());
35
36
  const remaining = MAX_TOTAL_CHARACTERS - characters;
37
+
36
38
  if (remaining <= 0) {
37
39
  truncated = true;
40
+
38
41
  break;
39
42
  }
43
+
40
44
  const text = message.text.length <= remaining ? message.text : `${message.text.slice(0, Math.max(0, remaining - 1))}…`;
41
45
  messages.unshift({ role: item.kind, text, createdAt: item.createdAt });
42
46
  characters += text.length;
43
47
  truncated ||= message.truncated || text.length < message.text.length;
44
48
  }
49
+
45
50
  return { messages, truncated };
46
51
  }
47
52
 
@@ -9,6 +9,7 @@ const useDismissals = create<{ byKey: Record<string, string> }>(() => ({ byKey:
9
9
 
10
10
  export function useResumeCompactionDismissal(chatId: string): string | null {
11
11
  const { keyOf } = useChatScope();
12
+
12
13
  return useDismissals((s) => s.byKey[keyOf(chatId)] ?? null);
13
14
  }
14
15
 
package/src/chat/stash.ts CHANGED
@@ -50,6 +50,7 @@ function attachments(value: unknown): StashedAttachment[] {
50
50
  if (!Array.isArray(value)) {
51
51
  return [];
52
52
  }
53
+
53
54
  return value.filter(isRecord).map((entry) => ({
54
55
  name: typeof entry.name === 'string' ? entry.name : '',
55
56
  mime: typeof entry.mime === 'string' ? entry.mime : '',
@@ -61,11 +62,13 @@ function stashedQuote(value: unknown): StashedQuote | undefined {
61
62
  if (!isRecord(value) || typeof value.text !== 'string' || typeof value.chatId !== 'string' || typeof value.itemId !== 'string') {
62
63
  return undefined;
63
64
  }
65
+
64
66
  return { text: value.text, chatId: value.chatId, itemId: value.itemId };
65
67
  }
66
68
 
67
69
  function stashedQuoteField(value: unknown): { quote?: StashedQuote } {
68
70
  const quote = stashedQuote(value);
71
+
69
72
  return quote === undefined ? {} : { quote };
70
73
  }
71
74
 
@@ -74,11 +77,14 @@ export function parseStash(raw: string | null): StashedPrompt[] {
74
77
  if (raw === null) {
75
78
  return [];
76
79
  }
80
+
77
81
  try {
78
82
  const stored: unknown = JSON.parse(raw);
83
+
79
84
  if (!Array.isArray(stored)) {
80
85
  return [];
81
86
  }
87
+
82
88
  return stored
83
89
  .filter(isRecord)
84
90
  .filter((entry) => typeof entry.id === 'string' && typeof entry.text === 'string')
@@ -112,7 +118,9 @@ export function stashedFrom(draft: ChatDraft, id: string, now: number, chatId: s
112
118
  if (isEmptyDraft(draft)) {
113
119
  return null;
114
120
  }
121
+
115
122
  const kept = draft.quote !== '' && draft.quoteSource !== null;
123
+
116
124
  return {
117
125
  id,
118
126
  text: kept ? draft.text : withQuote(draft.quote, draft.text),
@@ -136,6 +144,7 @@ export const STASH_CHATS_LIMIT = 16;
136
144
  export function restorableChats(stashed: readonly string[], referable: ReadonlySet<string>, chatId: string): { chats: string[]; dropped: number } {
137
145
  const others = stashed.filter((id) => id !== chatId);
138
146
  const chats = others.filter((id) => referable.has(id));
147
+
139
148
  return { chats: chats.slice(0, STASH_CHATS_LIMIT), dropped: others.length - chats.length };
140
149
  }
141
150
 
@@ -155,10 +164,13 @@ function write(prompts: StashedPrompt[]): void {
155
164
  /* Puts a draft on the shelf; false when the composer held nothing to put there. */
156
165
  export function stashDraft(draft: ChatDraft, chatId: string): boolean {
157
166
  const entry = stashedFrom(draft, `stash-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, Date.now(), chatId);
167
+
158
168
  if (!entry) {
159
169
  return false;
160
170
  }
171
+
161
172
  write(withStashed(useStash.getState().prompts, entry));
173
+
162
174
  return true;
163
175
  }
164
176
 
@@ -1,5 +1,6 @@
1
1
  import i18next from 'i18next';
2
2
  import type { ChatApprovalItem, ChatItem, ChatQuestionItem, ChatSubagentResult } from '@adecore/agent-contracts';
3
+ import { isWaiting } from '../state/chats';
3
4
  import { errorCode, isConnectionError, type ChatTransport } from '../transport';
4
5
 
5
6
  // What the panel opens on: the newest end of the conversation, which is where an agent that works is.
@@ -46,26 +47,28 @@ export function mergeNewest(
46
47
  pageCursor: string | null
47
48
  ): { items: ChatItem[]; cursor: string | null } {
48
49
  const known = new Map(current.map((item, index) => [item.id, index]));
50
+
49
51
  if (current.length > 0 && page.length > 0 && !page.some((item) => known.has(item.id))) {
50
52
  return { items: page, cursor: pageCursor };
51
53
  }
54
+
52
55
  const items = [...current];
56
+
53
57
  for (const item of page) {
54
58
  const index = known.get(item.id);
59
+
55
60
  if (index === undefined) {
56
61
  items.push(item);
57
62
  } else {
58
63
  items[index] = item;
59
64
  }
60
65
  }
66
+
61
67
  return { items, cursor: current.length === 0 ? pageCursor : currentCursor };
62
68
  }
63
69
 
64
70
  function pendingOf(items: readonly ChatItem[]): Array<ChatApprovalItem | ChatQuestionItem> {
65
- return items.filter(
66
- (item): item is ChatApprovalItem | ChatQuestionItem =>
67
- (item.kind === 'approval' && item.decision === 'pending') || (item.kind === 'question' && item.state === 'pending')
68
- );
71
+ return items.filter(isWaiting);
69
72
  }
70
73
 
71
74
  function messageOf(error: unknown): string {
@@ -115,6 +118,7 @@ export class SubagentConversation {
115
118
  }
116
119
  })
117
120
  );
121
+
118
122
  return this.enqueue(() => this.readNewest(true));
119
123
  }
120
124
 
@@ -123,9 +127,12 @@ export class SubagentConversation {
123
127
  if (this.refreshWaiting) {
124
128
  return this.queue;
125
129
  }
130
+
126
131
  this.refreshWaiting = true;
132
+
127
133
  return this.enqueue(() => {
128
134
  this.refreshWaiting = false;
135
+
129
136
  return this.readNewest(false);
130
137
  });
131
138
  }
@@ -134,10 +141,13 @@ export class SubagentConversation {
134
141
  // A page on its way answers every call made meanwhile; a scroll asks for one on every frame.
135
142
  this.earlier ??= this.enqueue(async () => {
136
143
  const cursor = this.state.cursor;
144
+
137
145
  if (cursor === null) {
138
146
  return;
139
147
  }
148
+
140
149
  this.set({ loadingEarlier: true });
150
+
141
151
  try {
142
152
  const page = await this.ask({ cursor, limit: SUBAGENT_PAGE });
143
153
  const items = [...page.items, ...this.state.items];
@@ -147,13 +157,16 @@ export class SubagentConversation {
147
157
  // The record was written again under this panel; what it holds now is the only truth.
148
158
  this.set({ items: [], cursor: null, loadingEarlier: false });
149
159
  await this.readNewest(false);
160
+
150
161
  return;
151
162
  }
163
+
152
164
  this.set({ loadingEarlier: false, error: messageOf(error) });
153
165
  }
154
166
  }).finally(() => {
155
167
  this.earlier = null;
156
168
  });
169
+
157
170
  return this.earlier;
158
171
  }
159
172
 
@@ -161,10 +174,13 @@ export class SubagentConversation {
161
174
  if (this.disposed) {
162
175
  return;
163
176
  }
177
+
164
178
  this.disposed = true;
179
+
165
180
  for (const off of this.unsubscribe.splice(0)) {
166
181
  off();
167
182
  }
183
+
168
184
  if (this.transport.status === 'open') {
169
185
  void this.transport.request('chat.subagent', { chatId: this.chatId, toolUseId: this.toolUseId, limit: 1, watch: false }).catch(() => undefined);
170
186
  }
@@ -174,6 +190,7 @@ export class SubagentConversation {
174
190
  try {
175
191
  const page = await this.ask({ limit: SUBAGENT_PAGE, ...(watch ? { watch: true } : {}) });
176
192
  const merged = mergeNewest(this.state.items, this.state.cursor, page.items, page.history.cursor);
193
+
177
194
  this.set({
178
195
  status: 'ready',
179
196
  items: merged.items,
@@ -189,6 +206,7 @@ export class SubagentConversation {
189
206
  // What is on screen stays; the socket that comes back asks again.
190
207
  return;
191
208
  }
209
+
192
210
  const unsupported = errorCode(error) === 'unknown-request';
193
211
  this.set({ status: 'failed', error: messageOf(error), unsupported });
194
212
  }
@@ -201,6 +219,7 @@ export class SubagentConversation {
201
219
  private enqueue(work: () => Promise<void>): Promise<void> {
202
220
  const next = this.queue.then(() => (this.disposed ? undefined : work()));
203
221
  this.queue = next.catch(() => undefined);
222
+
204
223
  return next;
205
224
  }
206
225
 
@@ -208,6 +227,7 @@ export class SubagentConversation {
208
227
  if (this.disposed) {
209
228
  return;
210
229
  }
230
+
211
231
  this.state = { ...this.state, ...patch };
212
232
  this.onChange(this.state);
213
233
  }
@@ -31,9 +31,11 @@ export function statusWordOf(item: ChatSubagentItem, task: SubagentTask | null):
31
31
  if (item.status === 'failed' && task?.status === 'cancelled') {
32
32
  return 'cancelled';
33
33
  }
34
+
34
35
  if (item.status === 'running' && task?.status === 'open' && task.paused !== undefined) {
35
36
  return 'paused';
36
37
  }
38
+
37
39
  return item.status;
38
40
  }
39
41
 
@@ -42,9 +44,11 @@ export type TaskRowState = StatusWord | 'waiting';
42
44
 
43
45
  export function taskRowStateOf(item: ChatSubagentItem, task: SubagentTask | null, child: ChatInfo | undefined): TaskRowState {
44
46
  const word = statusWordOf(item, task);
47
+
45
48
  if (word === 'running' && (child?.status === 'needs-you' || (child?.requests?.length ?? 0) > 0)) {
46
49
  return 'waiting';
47
50
  }
51
+
48
52
  return word;
49
53
  }
50
54
 
@@ -63,6 +67,7 @@ export function flyoutSubagents(order: readonly string[], structure: Readonly<Re
63
67
  return activeSubagents(
64
68
  order.flatMap((id) => {
65
69
  const item = structure[id];
70
+
66
71
  return item === undefined ? [] : [item];
67
72
  })
68
73
  );
@@ -76,6 +81,7 @@ export function summaryWordOf(words: readonly StatusWord[]): StatusWord {
76
81
  /* The number on the badge: the ones still at work, a paused one included, and all of them once none is. */
77
82
  export function badgeCountOf(words: readonly StatusWord[]): number {
78
83
  const active = words.filter((word) => word === 'running' || word === 'paused').length;
84
+
79
85
  return active > 0 ? active : words.length;
80
86
  }
81
87
 
@@ -85,18 +91,23 @@ export function entryTimeOf(item: ChatSubagentItem, task: SubagentTask | null, n
85
91
  const startedAt = task?.createdAt ?? item.startedAt;
86
92
  const finishedAt = task === null ? item.finishedAt : (task.settledAt ?? item.finishedAt);
87
93
  const word = statusWordOf(item, task);
94
+
88
95
  if (word === 'paused') {
89
96
  const until = task?.paused?.until;
97
+
90
98
  return until === undefined
91
99
  ? i18next.t('agent-chat:common.status.paused')
92
100
  : i18next.t('agent-chat:activity.pausedUntil', { time: formatMoment(until, now) });
93
101
  }
102
+
94
103
  if (item.status === 'running') {
95
104
  return startedAt > 0 ? formatElapsedShort(now - startedAt) : '';
96
105
  }
106
+
97
107
  if (startedAt <= 0 || finishedAt === null || finishedAt < startedAt) {
98
108
  return i18next.t(`agent-chat:common.status.${word}`);
99
109
  }
110
+
100
111
  return i18next.t(`agent-chat:activity.took.${word}`, { duration: formatElapsedShort(finishedAt - startedAt) });
101
112
  }
102
113
 
@@ -112,12 +123,15 @@ export function stopOf(item: ChatSubagentItem, turnRunning: boolean): SubagentSt
112
123
  if (item.status !== 'running') {
113
124
  return null;
114
125
  }
126
+
115
127
  if (item.origin === 'ruimte') {
116
128
  return item.childId === undefined ? null : 'task';
117
129
  }
130
+
118
131
  if (item.native?.agentId) {
119
132
  return 'native';
120
133
  }
134
+
121
135
  return turnRunning ? null : 'mark';
122
136
  }
123
137
 
@@ -16,6 +16,7 @@ export const useSubagentSupport = create<SubagentSupportStore>((set, get) => ({
16
16
  if (get().unsupported[scopeId]) {
17
17
  return;
18
18
  }
19
+
19
20
  set({ unsupported: { ...get().unsupported, [scopeId]: true } });
20
21
  }
21
22
  }));
@@ -77,6 +77,7 @@ export function subagentOf(structure: Readonly<Record<string, ChatItem>>, toolUs
77
77
  export function openableSubagents(order: readonly string[], structure: Record<string, ChatItem>, machineRefused: boolean): ChatSubagentItem[] {
78
78
  return order.flatMap((id) => {
79
79
  const item = structure[id];
80
+
80
81
  return item?.kind === 'subagent' && canOpenSubagent(item, machineRefused) ? [item] : [];
81
82
  });
82
83
  }
@@ -97,19 +98,24 @@ export const useSubagentView = create<SubagentViewStore>((set, get) => ({
97
98
  trails: {},
98
99
  show(key, trail) {
99
100
  const trails = { ...get().trails };
101
+
100
102
  if (trail.length === 0) {
101
103
  delete trails[key];
102
104
  } else {
103
105
  trails[key] = trail;
104
106
  }
107
+
105
108
  set({ trails });
106
109
  },
107
110
  back(key) {
108
111
  const trail = get().trails[key];
112
+
109
113
  if (trail === undefined || trail.length === 0) {
110
114
  return false;
111
115
  }
116
+
112
117
  get().show(key, stepBack(trail));
118
+
113
119
  return true;
114
120
  }
115
121
  }));
@@ -118,6 +124,7 @@ export const useSubagentView = create<SubagentViewStore>((set, get) => ({
118
124
  export function useSubagentTrail(chatId: string): { trail: SubagentTrail; show(trail: SubagentTrail): void } {
119
125
  const key = useChatScope().keyOf(chatId);
120
126
  const trail = useSubagentView((s) => s.trails[key] ?? MAIN_AGENT);
127
+
121
128
  return { trail, show: (next) => useSubagentView.getState().show(key, next) };
122
129
  }
123
130
 
@@ -127,10 +134,12 @@ export function useOpenableSubagents(chatId: string): ChatSubagentItem[] {
127
134
  // The structure and not the items, so a delta growing a reply does not walk the thread again.
128
135
  const order = useChatRow(chatId, (row) => row?.order);
129
136
  const structure = useChatRow(chatId, (row) => row?.structure);
137
+
130
138
  return structure === undefined ? [] : openableSubagents(order ?? NO_ITEMS, structure, refused);
131
139
  }
132
140
 
133
141
  export function useSubagentItem(chatId: string, toolUseId: string): ChatSubagentItem | null {
134
142
  const structure = useChatRow(chatId, (row) => row?.structure);
143
+
135
144
  return structure === undefined ? null : subagentOf(structure, toolUseId);
136
145
  }
@@ -18,6 +18,7 @@ export interface ThumbnailCrop {
18
18
  /* The square in the middle of an image that a thumbnail shows, the same part `object-cover` would. */
19
19
  export function thumbnailCrop(width: number, height: number, max: number = THUMBNAIL_PX): ThumbnailCrop {
20
20
  const size = Math.max(1, Math.min(width, height));
21
+
21
22
  return {
22
23
  sx: Math.max(0, Math.floor((width - size) / 2)),
23
24
  sy: Math.max(0, Math.floor((height - size) / 2)),
@@ -29,9 +30,11 @@ export function thumbnailCrop(width: number, height: number, max: number = THUMB
29
30
  function bytesOf(base64: string): Uint8Array<ArrayBuffer> {
30
31
  const binary = atob(base64);
31
32
  const bytes = new Uint8Array(new ArrayBuffer(binary.length));
33
+
32
34
  for (let i = 0; i < binary.length; i++) {
33
35
  bytes[i] = binary.charCodeAt(i);
34
36
  }
37
+
35
38
  return bytes;
36
39
  }
37
40
 
@@ -55,12 +58,16 @@ async function makeThumbnail(upload: ChatAttachmentUpload): Promise<string> {
55
58
  const crop = thumbnailCrop(bitmap.width, bitmap.height);
56
59
  const canvas = new OffscreenCanvas(crop.out, crop.out);
57
60
  const context = canvas.getContext('2d');
61
+
58
62
  if (!context) {
59
63
  bitmap.close();
64
+
60
65
  return uploadPreviewUrl(upload);
61
66
  }
67
+
62
68
  context.drawImage(bitmap, crop.sx, crop.sy, crop.size, crop.size, 0, 0, crop.out, crop.out);
63
69
  bitmap.close();
70
+
64
71
  return await dataUrlOf(await canvas.convertToBlob({ type: 'image/png' }));
65
72
  } catch {
66
73
  return uploadPreviewUrl(upload);
@@ -73,13 +80,17 @@ const making = new WeakMap<ChatAttachmentUpload, Promise<string>>();
73
80
 
74
81
  function thumbnailOf(upload: ChatAttachmentUpload): Promise<string> {
75
82
  let promise = making.get(upload);
83
+
76
84
  if (!promise) {
77
85
  promise = makeThumbnail(upload).then((url) => {
78
86
  made.set(upload, url);
87
+
79
88
  return url;
80
89
  });
90
+
81
91
  making.set(upload, promise);
82
92
  }
93
+
83
94
  return promise;
84
95
  }
85
96
 
@@ -92,12 +103,15 @@ export function useUploadThumbnail(upload: ChatAttachmentUpload): string | null
92
103
  if (made.has(upload)) {
93
104
  return;
94
105
  }
106
+
95
107
  let cancelled = false;
108
+
96
109
  void thumbnailOf(upload).then((url) => {
97
110
  if (!cancelled) {
98
111
  setReady({ upload, url });
99
112
  }
100
113
  });
114
+
101
115
  return () => {
102
116
  cancelled = true;
103
117
  };
@@ -25,11 +25,13 @@ export const useTimelineFlash = create<TimelineFlashStore>((set, get) => ({
25
25
  flash(key, rowId, opens, block) {
26
26
  const nonce = (get().target?.nonce ?? 0) + 1;
27
27
  set({ target: { key, rowId, opens, nonce, ...(block ? { block } : {}) } });
28
+
28
29
  const timer = setTimeout(() => {
29
30
  if (get().target?.nonce === nonce) {
30
31
  set({ target: null });
31
32
  }
32
33
  }, FLASH_MS);
34
+
33
35
  (timer as { unref?: () => void }).unref?.();
34
36
  }
35
37
  }));
@@ -27,7 +27,9 @@ export function registerTimeline(
27
27
  if (element === null) {
28
28
  return () => undefined;
29
29
  }
30
+
30
31
  scrollers.set(chatId, { element, follow, coveredHeight });
32
+
31
33
  return () => {
32
34
  if (scrollers.get(chatId)?.element === element) {
33
35
  scrollers.delete(chatId);
@@ -40,7 +42,9 @@ export function setTimelineAtEnd(chatId: string, atEnd: boolean): void {
40
42
  if (ends.get(chatId) === atEnd) {
41
43
  return;
42
44
  }
45
+
43
46
  ends.set(chatId, atEnd);
47
+
44
48
  for (const listener of listeners) {
45
49
  listener();
46
50
  }
@@ -48,6 +52,7 @@ export function setTimelineAtEnd(chatId: string, atEnd: boolean): void {
48
52
 
49
53
  export function subscribeTimelineEnd(listener: () => void): () => void {
50
54
  listeners.add(listener);
55
+
51
56
  return () => {
52
57
  listeners.delete(listener);
53
58
  };
@@ -60,6 +65,7 @@ export function timelineAtEnd(chatId: string): boolean {
60
65
 
61
66
  export function scrollTimelineToEnd(chatId: string, behavior: ScrollBehavior = 'smooth'): void {
62
67
  const scroller = scrollers.get(chatId);
68
+
63
69
  if (scroller) {
64
70
  scroller.follow(true);
65
71
  scroller.element.scrollTo({ top: scroller.element.scrollHeight, behavior });
@@ -69,12 +75,15 @@ export function scrollTimelineToEnd(chatId: string, behavior: ScrollBehavior = '
69
75
  /* Pages the thread of this chat up or down; false when it has no timeline on screen. */
70
76
  export function pageTimeline(chatId: string, direction: -1 | 1): boolean {
71
77
  const scroller = scrollers.get(chatId);
78
+
72
79
  if (!scroller) {
73
80
  return false;
74
81
  }
82
+
75
83
  scroller.follow(false);
76
84
  const { element } = scroller;
77
85
  element.scrollBy({ top: direction * Math.max(0, element.clientHeight - scroller.coveredHeight()) * PAGE_OVERLAP, behavior: 'smooth' });
86
+
78
87
  return true;
79
88
  }
80
89
 
@@ -87,6 +96,7 @@ const steppers = new Map<string, (direction: -1 | 1) => boolean>();
87
96
 
88
97
  export function registerMessageStepper(key: string, step: (direction: -1 | 1) => boolean): () => void {
89
98
  steppers.set(key, step);
99
+
90
100
  return () => {
91
101
  if (steppers.get(key) === step) {
92
102
  steppers.delete(key);
@@ -107,17 +117,21 @@ export function stepTimelineMessage(key: string, direction: -1 | 1): boolean {
107
117
  function waitingJumps<T>(): { register(key: string, jump: (target: T) => void): () => void; jump(key: string, target: T): boolean } {
108
118
  const jumpers = new Map<string, (target: T) => void>();
109
119
  const waiting = new Map<string, { target: T; at: number }>();
120
+
110
121
  return {
111
122
  register(key, jump) {
112
123
  jumpers.set(key, jump);
113
124
  const pending = waiting.get(key);
125
+
114
126
  if (pending !== undefined) {
115
127
  waiting.delete(key);
128
+
116
129
  // A jump nobody picked up for long is not what the person wants any more.
117
130
  if (Date.now() - pending.at <= PENDING_JUMP_TTL_MS) {
118
131
  jump(pending.target);
119
132
  }
120
133
  }
134
+
121
135
  return () => {
122
136
  if (jumpers.get(key) === jump) {
123
137
  jumpers.delete(key);
@@ -126,11 +140,15 @@ function waitingJumps<T>(): { register(key: string, jump: (target: T) => void):
126
140
  },
127
141
  jump(key, target) {
128
142
  const jump = jumpers.get(key);
143
+
129
144
  if (jump === undefined) {
130
145
  waiting.set(key, { target, at: Date.now() });
146
+
131
147
  return false;
132
148
  }
149
+
133
150
  jump(target);
151
+
134
152
  return true;
135
153
  }
136
154
  };
@@ -159,6 +177,7 @@ export function jumpToTimelineItem(key: string, itemId: string, options: ItemJum
159
177
  export type TurnTarget = 'prompt' | 'changes';
160
178
 
161
179
  type TurnJump = (turnId: string, target: TurnTarget) => void;
180
+
162
181
  const turnJumps = waitingJumps<{ turnId: string; target: TurnTarget }>();
163
182
 
164
183
  export function registerTurnJumper(key: string, jump: TurnJump): () => void {
@@ -186,18 +205,22 @@ export interface ReadingAnchor {
186
205
  /* The first row in view, found by the rows' `data-item-id`, for a thread that keeps every row in the document. */
187
206
  export function firstRowInView(scroller: HTMLElement): ReadingAnchor | null {
188
207
  const top = scroller.getBoundingClientRect().top;
208
+
189
209
  for (const row of scroller.querySelectorAll<HTMLElement>('[data-item-id]')) {
190
210
  const rect = row.getBoundingClientRect();
211
+
191
212
  if (rect.bottom > top) {
192
213
  return { id: row.dataset.itemId!, offset: rect.top - top };
193
214
  }
194
215
  }
216
+
195
217
  return null;
196
218
  }
197
219
 
198
220
  /* Puts the anchored row back where it was; nothing moves when that row is gone. */
199
221
  export function restoreAnchor(scroller: HTMLElement, anchor: ReadingAnchor): void {
200
222
  const row = scroller.querySelector<HTMLElement>(`[data-item-id="${CSS.escape(anchor.id)}"]`);
223
+
201
224
  if (row !== null) {
202
225
  scroller.scrollTop += row.getBoundingClientRect().top - scroller.getBoundingClientRect().top - anchor.offset;
203
226
  }
@@ -9,10 +9,13 @@ import { Pill, Tooltip } from '@adecore/ui';
9
9
  function AccountPillOf({ provider, account }: { provider: AgentKind; account: string | undefined }) {
10
10
  const { t } = useTranslation('agent-chat');
11
11
  const choice = useAccountChoice(provider, account);
12
+
12
13
  if (choice === null) {
13
14
  return null;
14
15
  }
16
+
15
17
  const name = choice.current === null ? choice.currentId : choice.nameOf(choice.current);
18
+
16
19
  return (
17
20
  <Tooltip label={t('account.pill', { provider: choice.providerName, account: name })}>
18
21
  <Pill icon={<AccountDot color={choice.current?.account.color} className="size-1.75" />}>
@@ -27,5 +30,6 @@ export function AccountPill({ chatId }: { chatId: string }) {
27
30
  const { keyOf } = useChatScope();
28
31
  const provider = useChats((s) => s.statusByKey[keyOf(chatId)]?.info.provider);
29
32
  const account = useChats((s) => s.statusByKey[keyOf(chatId)]?.info.account);
33
+
30
34
  return provider === undefined ? null : <AccountPillOf provider={provider} account={account} />;
31
35
  }
@@ -4,6 +4,7 @@ import { parseAnsi } from '../logic/ansi';
4
4
  export function AnsiOutput({ text, limit, tailLines }: { text: string; limit?: number; tailLines?: number }) {
5
5
  const { t } = useTranslation('agent-chat');
6
6
  const { tokens, omitted } = parseAnsi(text, { limit, tailLines });
7
+
7
8
  return (
8
9
  <>
9
10
  {tokens.map((token, index) => (