@adecore/agents-react 0.39.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 (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 +26 -26
  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
@@ -37,12 +37,15 @@ interface MarkdownNode {
37
37
  /* The command a shell fence holds, read from the fence as written, so it keeps the indentation and line endings it has. */
38
38
  function shellFence(source: string): ShellFence | null {
39
39
  const opening = /^ {0,3}(`{3,}|~{3,})[ \t]*(sh|bash|zsh)[ \t]*\r?\n/.exec(source);
40
+
40
41
  if (opening === null) {
41
42
  return null;
42
43
  }
44
+
43
45
  const body = source.slice(opening[0].length);
44
46
  const closing = /(?:^|\r?\n) {0,3}(`{3,}|~{3,})[ \t]*$/.exec(body);
45
47
  const closed = closing !== null && closing[1]![0] === opening[1]![0] && closing[1]!.length >= opening[1]!.length;
48
+
46
49
  return { language: opening[2] as ShellFence['language'], code: closed ? body.slice(0, closing.index) : body, closed };
47
50
  }
48
51
 
@@ -53,15 +56,20 @@ function remarkShellCodeBlocks() {
53
56
  if (node.type !== 'code') {
54
57
  continue;
55
58
  }
59
+
56
60
  const start = node.position?.start.offset;
57
61
  const end = node.position?.end.offset;
62
+
58
63
  if (start === undefined || end === undefined) {
59
64
  continue;
60
65
  }
66
+
61
67
  const shell = shellFence(String(file.value).slice(start, end));
68
+
62
69
  if (shell === null) {
63
70
  continue;
64
71
  }
72
+
65
73
  node.data = {
66
74
  ...node.data,
67
75
  hProperties: {
@@ -80,7 +88,9 @@ function shellCodeBlockActions(reply: ShellCodeBlockReply | null, shell: ShellFe
80
88
  if (reply === null || shell === null) {
81
89
  return undefined;
82
90
  }
91
+
83
92
  const { scopeId, chatId, itemId } = reply;
93
+
84
94
  return render?.({ scopeId, chatId, itemId, language: shell.language, code: shell.code, complete: !reply.streaming && shell.closed });
85
95
  }
86
96
 
@@ -98,6 +108,7 @@ function FileLink({ target, className, children }: { target: FileRef; className?
98
108
  const cwd = useFileLinkCwd();
99
109
  const scopeId = useFileLinkScopeId();
100
110
  const openFileLink = useOpenFileLink();
111
+
101
112
  return (
102
113
  // A button is not selectable on its own, and a thread is copied as often as it is clicked.
103
114
  <button
@@ -120,9 +131,11 @@ function FileLink({ target, className, children }: { target: FileRef; className?
120
131
  /* Inline code, which is a file reference often enough that it is worth asking every time. */
121
132
  function InlineCode({ text }: { text: string }) {
122
133
  const target = useFileLinkTarget(text);
134
+
123
135
  if (target === null) {
124
136
  return <code className={INLINE_CODE}>{text}</code>;
125
137
  }
138
+
126
139
  return (
127
140
  // The chip sits on a code inside the button, since a button is always a box of its own and its
128
141
  // background would fill the whole box once a long path wraps.
@@ -138,6 +151,7 @@ function InlineCode({ text }: { text: string }) {
138
151
  */
139
152
  function hrefPath(href: string): string {
140
153
  const bare = href.replace(/^file:\/\//, '');
154
+
141
155
  try {
142
156
  return decodeURI(bare);
143
157
  } catch {
@@ -148,6 +162,7 @@ function hrefPath(href: string): string {
148
162
  /* A markdown link, which is a file when its target names no scheme of its own. */
149
163
  function MarkdownLink({ href, children }: { href?: string; children?: ReactNode }) {
150
164
  const target = useFileLinkTarget(hrefPath(href ?? ''));
165
+
151
166
  if (target !== null) {
152
167
  return (
153
168
  <FileLink target={target} className="text-accent underline underline-offset-2">
@@ -155,6 +170,7 @@ function MarkdownLink({ href, children }: { href?: string; children?: ReactNode
155
170
  </FileLink>
156
171
  );
157
172
  }
173
+
158
174
  return <ExternalLink href={href}>{children}</ExternalLink>;
159
175
  }
160
176
 
@@ -174,13 +190,17 @@ function localImagePath(src: string, cwd: string | null): string | null {
174
190
  if (src === '' || /^[a-z][a-z0-9+.-]*:/i.test(src)) {
175
191
  return null;
176
192
  }
193
+
177
194
  const target = hrefPath(src);
195
+
178
196
  if (target.startsWith('/')) {
179
197
  return target;
180
198
  }
199
+
181
200
  if (cwd === null) {
182
201
  return null;
183
202
  }
203
+
184
204
  return `${cwd.replace(/\/+$/, '')}/${target.replace(/^\.\//, '')}`;
185
205
  }
186
206
 
@@ -189,9 +209,11 @@ function MarkdownImage({ src, alt }: { src?: string; alt?: string }) {
189
209
  const { MarkdownImage: HostImage } = useChatHost();
190
210
  const cwd = useFileLinkCwd();
191
211
  const path = typeof src === 'string' ? localImagePath(src, cwd) : null;
212
+
192
213
  if (HostImage === undefined || path === null) {
193
214
  return <img src={src} alt={alt} />;
194
215
  }
216
+
195
217
  return <HostImage path={path} alt={alt ?? ''} />;
196
218
  }
197
219
 
@@ -202,6 +224,7 @@ function PlainImage({ src, alt }: { src?: string; alt?: string }) {
202
224
  /* A ```mermaid fence, which draws what it says. */
203
225
  function Diagram({ code }: { code: string }) {
204
226
  const { t } = useTranslation('agent-chat');
227
+
205
228
  return (
206
229
  <ErrorBoundary label={t('code.mermaid.crashed')} resetKeys={[code]} compact className="rounded-lg border border-border">
207
230
  <MermaidBlock code={code} />
@@ -212,11 +235,14 @@ function Diagram({ code }: { code: string }) {
212
235
  function MarkdownCode({ className, children, plain = false }: { className?: string; children?: ReactNode; plain?: boolean }) {
213
236
  const block = useContext(BlockCodeContext);
214
237
  const text = String(children ?? '');
238
+
215
239
  if (block) {
216
240
  const lang = languageOf(className);
217
241
  const code = text.replace(/\n$/, '');
242
+
218
243
  return lang === 'mermaid' ? <Diagram code={code} /> : <CodeBlock code={code} lang={lang} />;
219
244
  }
245
+
220
246
  return plain ? <code className={INLINE_CODE}>{text}</code> : <InlineCode text={text} />;
221
247
  }
222
248
 
@@ -259,6 +285,7 @@ const REPLY_HEADING_OFFSET = 3;
259
285
 
260
286
  function replyHeading(level: 1 | 2 | 3 | 4 | 5 | 6) {
261
287
  const Tag = `h${level}` as const;
288
+
262
289
  return function ReplyHeading({ children }: { children?: ReactNode }) {
263
290
  return <Tag aria-level={Math.min(6, level + REPLY_HEADING_OFFSET)}>{children}</Tag>;
264
291
  };
@@ -283,17 +310,22 @@ function ReplyCode({ className, children, node }: { className?: string; children
283
310
  const properties = node?.properties;
284
311
  const language = properties?.['data-shell-language'];
285
312
  const code = properties?.['data-shell-code'];
313
+
286
314
  const shell: ShellFence | null =
287
315
  (language === 'sh' || language === 'bash' || language === 'zsh') && typeof code === 'string'
288
316
  ? { language, code, closed: properties?.['data-shell-closed'] === true }
289
317
  : null;
318
+
290
319
  const text = String(children ?? '');
320
+
291
321
  if (block && languageOf(className) === 'mermaid') {
292
322
  return <Diagram code={text.replace(/\n$/, '')} />;
293
323
  }
324
+
294
325
  if (block) {
295
326
  return <CodeBlock code={text.replace(/\n$/, '')} lang={languageOf(className)} actions={shellCodeBlockActions(reply, shell, renderShellCodeBlock)} />;
296
327
  }
328
+
297
329
  return <InlineCode text={text} />;
298
330
  }
299
331
 
@@ -302,6 +334,7 @@ function Chip({ kind, value }: { kind: string; value: string }) {
302
334
  if (kind === 'reference') {
303
335
  return <ReferenceChip id={value} />;
304
336
  }
337
+
305
338
  if (kind === 'skill') {
306
339
  return (
307
340
  <TokenChip
@@ -314,6 +347,7 @@ function Chip({ kind, value }: { kind: string; value: string }) {
314
347
  />
315
348
  );
316
349
  }
350
+
317
351
  return (
318
352
  // The path is what the thread's menu opens in the preview from here.
319
353
  <TokenChip
@@ -336,6 +370,7 @@ function ChipSpan({ children, node: _node, 'data-chip': chip, 'data-value': valu
336
370
  if (chip === undefined || value === undefined) {
337
371
  return <span {...rest}>{children}</span>;
338
372
  }
373
+
339
374
  return <Chip kind={chip} value={value} />;
340
375
  }
341
376
 
@@ -352,8 +387,10 @@ const ReplyBlock = memo(function ReplyBlock({ text, fade, open, reply }: { text:
352
387
  const { items, referenced } = useContext(ReferenceLookupContext);
353
388
  // Chips first, so the fade wraps the words around a chip and never cuts a name in two.
354
389
  const named: NamedReferenceOptions = { items, referenced };
390
+
355
391
  const rehypePlugins: Options['rehypePlugins'] =
356
392
  items.length === 0 ? (fade ? FADE_PLUGINS : NO_PLUGINS) : [[rehypeNamedReferences, named], ...(fade ? FADE_PLUGINS : [])];
393
+
357
394
  return (
358
395
  // A context rather than a second set of components. A component that changed would mount the
359
396
  // code block again the moment its fence closes, and it would lose the lines it already has.
@@ -389,6 +426,7 @@ const ReplyFence = memo(function ReplyFence({
389
426
  // The class markdown gives the code, read back the way a parsed fence's is.
390
427
  const lang = languageOf(`language-${info.split(/[ \t]/, 1)[0]}`);
391
428
  const shell = reply === undefined ? null : shellFence(source);
429
+
392
430
  return (
393
431
  <CodeStreamingContext.Provider value={open}>
394
432
  {lang === 'mermaid' ? (
@@ -418,6 +456,7 @@ export const ReplyMarkdown = memo(function ReplyMarkdown({
418
456
  reply?: ShellCodeBlockReply;
419
457
  }) {
420
458
  const blocks = splitMarkdownBlocks(text);
459
+
421
460
  return (
422
461
  <div className="chat-markdown prose prose-sm max-w-none text-sm" data-arriving={arriving || undefined}>
423
462
  {blocks.map((block, index) =>
@@ -455,6 +494,7 @@ export const MessageMarkdown = memo(function MessageMarkdown({
455
494
  references?: readonly ReferenceToken[];
456
495
  }) {
457
496
  const rehypePlugins: [typeof rehypeChips, ChipOptions][] = [[rehypeChips, { mentions, skills, references }]];
497
+
458
498
  return (
459
499
  <div className="chat-markdown prose prose-sm max-w-none text-sm">
460
500
  <ReactMarkdown remarkPlugins={MESSAGE_PLUGINS} rehypePlugins={rehypePlugins} components={replyComponents}>
@@ -484,6 +524,7 @@ export const Markdown = memo(function Markdown({
484
524
  componentOverrides?: Components;
485
525
  }) {
486
526
  const renderComponents = { ...(fileLinks ? components : plainComponents), ...componentOverrides };
527
+
487
528
  return (
488
529
  <div className="chat-markdown prose prose-sm max-w-none text-sm" data-muted={muted || undefined}>
489
530
  <ReactMarkdown remarkPlugins={breaks ? PLUGINS_WITH_BREAKS : PLUGINS} rehypePlugins={rehypePlugins} components={renderComponents}>
@@ -25,14 +25,17 @@ function resolveColor(element: HTMLElement, token: string, ground: string): stri
25
25
  probeCanvas.height = 1;
26
26
  const context = probeCanvas.getContext('2d', { willReadFrequently: true });
27
27
  const value = getComputedStyle(element).getPropertyValue(token).trim();
28
+
28
29
  if (context === null || value === '') {
29
30
  return ground;
30
31
  }
32
+
31
33
  context.fillStyle = ground;
32
34
  context.fillRect(0, 0, 1, 1);
33
35
  context.fillStyle = value;
34
36
  context.fillRect(0, 0, 1, 1);
35
37
  const [red, green, blue] = context.getImageData(0, 0, 1, 1).data;
38
+
36
39
  return `#${[red, green, blue].map((channel) => channel!.toString(16).padStart(2, '0')).join('')}`;
37
40
  }
38
41
 
@@ -45,14 +48,17 @@ interface DiagramLook {
45
48
  function diagramLook(element: HTMLElement): DiagramLook {
46
49
  const style = getComputedStyle(element);
47
50
  const ground = resolveColor(element, GROUND_TOKEN, '#808080');
51
+
48
52
  const colors = Object.fromEntries(
49
53
  Object.entries(DIAGRAM_TOKENS).map(([name, token]) => [name, token === GROUND_TOKEN ? ground : resolveColor(element, token, ground)])
50
54
  );
55
+
51
56
  const font = {
52
57
  family: style.fontFamily,
53
58
  mono: style.getPropertyValue('--font-mono').trim() || 'monospace',
54
59
  size: Number.parseFloat(style.fontSize) || 14
55
60
  };
61
+
56
62
  return { colors, font };
57
63
  }
58
64
 
@@ -64,6 +70,7 @@ function keepingSame(next: DiagramLook) {
64
70
  async function layOut(id: string, source: string, font: DiagramFont): Promise<LaidOut> {
65
71
  const { renderAsync } = await import('pele/lazy');
66
72
  const { svg, width, height } = await renderAsync(source, renderOptions(font, id));
73
+
67
74
  return { svg, width: Math.ceil(width), height: Math.ceil(height) };
68
75
  }
69
76
 
@@ -84,6 +91,7 @@ export function MermaidBlock({ code }: { code: string }) {
84
91
 
85
92
  useEffect(() => {
86
93
  const element = frame.current;
94
+
87
95
  if (element !== null) {
88
96
  setLook(keepingSame(diagramLook(element)));
89
97
  }
@@ -92,25 +100,31 @@ export function MermaidBlock({ code }: { code: string }) {
92
100
  // An app may change its colors without turning light or dark, which only the root of the page tells.
93
101
  useEffect(() => {
94
102
  const element = frame.current;
103
+
95
104
  if (element === null) {
96
105
  return;
97
106
  }
107
+
98
108
  return watchTheme(element.ownerDocument, () => setLook(keepingSame(diagramLook(element))));
99
109
  }, []);
100
110
 
101
111
  const fontFamily = look?.font.family;
102
112
  const fontMono = look?.font.mono;
103
113
  const fontSize = look?.font.size;
114
+
104
115
  useEffect(() => {
105
116
  if (streaming || fontFamily === undefined || fontMono === undefined || fontSize === undefined) {
106
117
  return;
107
118
  }
119
+
108
120
  let cancelled = false;
121
+
109
122
  // A diagram already drawn stays until the next one is there, so it does not blink.
110
123
  layOut(id, code, { family: fontFamily, mono: fontMono, size: fontSize }).then(
111
124
  (image) => !cancelled && setDrawing({ state: 'drawn', image }),
112
125
  () => !cancelled && setDrawing({ state: 'failed' })
113
126
  );
127
+
114
128
  return () => {
115
129
  cancelled = true;
116
130
  };
@@ -118,6 +132,7 @@ export function MermaidBlock({ code }: { code: string }) {
118
132
 
119
133
  const image = drawing.state === 'drawn' && look !== null ? { ...drawing.image, svg: themedSvg(drawing.image.svg, look.colors) } : null;
120
134
  const showsDiagram = !streaming && !source && drawing.state !== 'failed';
135
+
121
136
  const toggle =
122
137
  streaming || drawing.state === 'failed' ? undefined : (
123
138
  <IconButton
@@ -127,6 +142,7 @@ export function MermaidBlock({ code }: { code: string }) {
127
142
  onClick={() => setSource((shown) => !shown)}
128
143
  />
129
144
  );
145
+
130
146
  return (
131
147
  // One tree in every state, so the code block keeps the lines it has when the fence closes.
132
148
  <div ref={frame}>
@@ -31,14 +31,19 @@ export function MessageActions({ chatId, row }: { chatId: string; row: MessageRo
31
31
  const bookmarks = useChatRow(chatId, (chat) => chat?.bookmarks);
32
32
  const info = useChatRow(chatId, (chat) => chat?.info ?? null);
33
33
  const turn = useChatRow(chatId, (chat) => (turnId === null ? undefined : chat?.structure[turnId]));
34
+
34
35
  const streaming = useChatRow(chatId, (chat) => {
35
36
  const item = chat?.items[row.id];
37
+
36
38
  return item?.kind === 'assistant' && item.streaming;
37
39
  });
40
+
38
41
  const hasText = useChatRow(chatId, (chat) => {
39
42
  const item = chat?.items[row.id];
43
+
40
44
  return (item?.kind === 'user' || item?.kind === 'assistant') && /\S/.test(item.text);
41
45
  });
46
+
42
47
  const bookmarked = bookmarks?.some((candidate) => candidate.itemId === row.id) === true;
43
48
  const canFork = turnId !== null && forkRefusal(info, turn) === null;
44
49
  const [copied, setCopied] = useState(false);
@@ -48,7 +53,9 @@ export function MessageActions({ chatId, row }: { chatId: string; row: MessageRo
48
53
  if (!copied) {
49
54
  return;
50
55
  }
56
+
51
57
  const timer = setTimeout(() => setCopied(false), COPIED_MS);
58
+
52
59
  return () => clearTimeout(timer);
53
60
  }, [copied]);
54
61
 
@@ -57,6 +64,7 @@ export function MessageActions({ chatId, row }: { chatId: string; row: MessageRo
57
64
  const item = useChats.getState().byKey[scope.keyOf(chatId)]?.items[row.id];
58
65
  const current = item?.kind === row.kind ? ({ ...row, item } as MessageRow) : row;
59
66
  const text = (plain ? null : markdownOf(current)) ?? messageTextOf(current);
67
+
60
68
  if (text !== null) {
61
69
  copyWithChips(messageClipboardChips(current.item, text, lookup));
62
70
  setCopied(true);
@@ -6,6 +6,7 @@ import { Icon, Pill, Tooltip } from '@adecore/ui';
6
6
  /* What a model row says beside the name: its badge, and a mark when the provider says the model can call tools. */
7
7
  export function ModelMarks({ model }: { model: ModelInfo }) {
8
8
  const { t } = useTranslation('agent-chat');
9
+
9
10
  return (
10
11
  <>
11
12
  {model.badge && (
@@ -77,19 +77,26 @@ export function ModelPicker({
77
77
  const trimmed = query.trim().toLowerCase();
78
78
 
79
79
  const favorites = useChatPreferences((preferences) => preferences.favoriteModels);
80
+
80
81
  const starredHere = (row: PickerEntry | undefined): boolean => row?.kind === 'model' && favorites[row.provider.kind]?.includes(row.model.slug) === true;
82
+
81
83
  const entries = pickerEntriesOf(inPickerOrder(providers), trimmed, legacyOpen, expanded, { provider, model: selection.model }, (kind) =>
82
84
  favoriteModelsOf(favorites, kind)
83
85
  );
86
+
84
87
  const scope = useContext(ChatScopeContext);
85
88
  const machine = useHostMachineLabel(scope?.id ?? '');
86
89
  const reported = useReportedModels(provider);
90
+
87
91
  const requirementOf = (entry: PickerEntry & { kind: 'model' }) => modelRequirementOf(entry.model, entry.provider, reported);
92
+
88
93
  // A CLI that is too old blocks all its models; one way to its settings under the group says it once.
89
94
  const kindBefore = (i: number): AgentKind | undefined => {
90
95
  const before = entries[i - 1];
96
+
91
97
  return before === undefined || before.kind === 'legacy' ? undefined : before.provider.kind;
92
98
  };
99
+
93
100
  const blockedKinds = new Set(entries.flatMap((entry) => (entry.kind === 'model' && requirementOf(entry) !== null ? [entry.provider.kind] : [])));
94
101
 
95
102
  /* Closing forgets the search, so the next open (a click or `/model`) starts on the whole list. */
@@ -99,6 +106,7 @@ export function ModelPicker({
99
106
  setLegacyOpen(false);
100
107
  setExpanded(new Set());
101
108
  }
109
+
102
110
  setIndex(0);
103
111
  onOpenChange(next);
104
112
  };
@@ -112,26 +120,36 @@ export function ModelPicker({
112
120
  if (!entry) {
113
121
  return;
114
122
  }
123
+
115
124
  if (entry.kind === 'legacy') {
116
125
  setLegacyOpen(true);
117
126
  inputRef.current?.focus();
127
+
118
128
  return;
119
129
  }
130
+
120
131
  if (entry.kind === 'all') {
121
132
  const kind = entry.provider.kind;
133
+
122
134
  setExpanded((now) => {
123
135
  const next = new Set(now);
136
+
124
137
  if (!next.delete(kind)) {
125
138
  next.add(kind);
126
139
  }
140
+
127
141
  return next;
128
142
  });
143
+
129
144
  inputRef.current?.focus();
145
+
130
146
  return;
131
147
  }
148
+
132
149
  if (requirementOf(entry) !== null) {
133
150
  return;
134
151
  }
152
+
135
153
  onChange(entry.provider.kind, entry.model.slug);
136
154
  setOpen(false);
137
155
  };
@@ -193,6 +211,7 @@ export function ModelPicker({
193
211
  if (entry.kind === 'all') {
194
212
  const before = entries[i - 1];
195
213
  const header = grouped && (before === undefined || before.kind === 'legacy' || before.provider.kind !== entry.provider.kind);
214
+
196
215
  return (
197
216
  <div key={`all-${entry.provider.kind}`}>
198
217
  {separatesGroups(kindBefore(i), entry.provider.kind) && <Menu.Separator />}
@@ -215,6 +234,7 @@ export function ModelPicker({
215
234
  </div>
216
235
  );
217
236
  }
237
+
218
238
  if (entry.kind === 'legacy') {
219
239
  return (
220
240
  <button
@@ -229,11 +249,13 @@ export function ModelPicker({
229
249
  </button>
230
250
  );
231
251
  }
252
+
232
253
  const previous = entries[i - 1];
233
254
  // A provider's rows start where the one before it ended; its row that opens the rest belongs to it.
234
255
  const first = grouped && (previous === undefined || previous.kind === 'legacy' || previous.provider.kind !== entry.provider.kind);
235
256
  // The makers read as groups only where the whole list is shown, so a short list is not cut by labels.
236
257
  const vendorOpen = trimmed !== '' || expanded.has(entry.provider.kind);
258
+
237
259
  // The starred rows lead their CLI without a maker's label; the makers start after them.
238
260
  const vendorFirst =
239
261
  vendorOpen &&
@@ -244,11 +266,13 @@ export function ModelPicker({
244
266
  previous.provider.kind !== entry.provider.kind ||
245
267
  starredHere(previous) ||
246
268
  previous.model.vendor !== entry.model.vendor);
269
+
247
270
  const requirement = requirementOf(entry);
248
271
  const next = entries[i + 1];
249
272
  const lastOfGroup = next === undefined || next.kind !== 'model' || next.provider.kind !== entry.provider.kind;
250
273
  const chosen = entry.provider.kind === provider && entry.model.slug === selection.model;
251
274
  const starred = starredHere(entry);
275
+
252
276
  return (
253
277
  <div key={`${entry.provider.kind}/${entry.model.slug}`}>
254
278
  {separatesGroups(kindBefore(i), entry.provider.kind) && <Menu.Separator />}
@@ -325,9 +349,11 @@ export function ModelPicker({
325
349
  export function StashPicker({ onRestore }: { onRestore(prompt: StashedPrompt): void }) {
326
350
  const { t } = useTranslation('agent-chat');
327
351
  const prompts = useStash((s) => s.prompts);
352
+
328
353
  if (prompts.length === 0) {
329
354
  return null;
330
355
  }
356
+
331
357
  return (
332
358
  <Popover.Root>
333
359
  <Tooltip label={t('pickers.stash.title')} kbd={STASH_SHORTCUT}>
@@ -82,6 +82,7 @@ export function PromptComposer({
82
82
  if (hold?.releasedBy === 'blur' && heldKey.current === activeKey) {
83
83
  return;
84
84
  }
85
+
85
86
  if (focused && expanded && !elsewhere && !ref.current?.querySelector('.prompt-card')?.contains(document.activeElement)) {
86
87
  // Keys meant for the draft may still be coming, and on a button the next Space would answer the card.
87
88
  if (hasDraft) {
@@ -114,6 +115,7 @@ export function PromptComposer({
114
115
  onDraft={(draft) => session.setDraft(promptIdOf(active), draft)}
115
116
  onAction={(action) => {
116
117
  refocus.hold();
118
+
117
119
  void session
118
120
  .act(active, () => answerPrompt(active, action, actions, host))
119
121
  .then((result) => {
@@ -76,23 +76,29 @@ export function PromptEditor({
76
76
  referables,
77
77
  label
78
78
  });
79
+
79
80
  const extensions = chipDecorations({ mentions: value.mentions, skills: value.skills, references: menus.referenceChips });
80
81
 
81
82
  const onKeyDown = (e: KeyboardEvent): boolean => {
82
83
  if (e.isComposing) {
83
84
  return false;
84
85
  }
86
+
85
87
  // Escape closes the picker, and only that: the dialog around the field stays.
86
88
  if (e.key === 'Escape' && menus.menuOpen) {
87
89
  menus.close();
88
90
  e.stopPropagation();
91
+
89
92
  return true;
90
93
  }
94
+
91
95
  // Enter adds a line here rather than sending, so on a command it completes like Tab.
92
96
  if (e.key === 'Enter' && menus.commandMenuOpen) {
93
97
  menus.completeCommand(menus.commands[menus.activeIndex]!);
98
+
94
99
  return true;
95
100
  }
101
+
96
102
  return pickInMenu(e, menus);
97
103
  };
98
104
 
@@ -18,6 +18,7 @@ interface Offer {
18
18
  /* The last line the range covers. A triple-click ends on an empty rect at the start of the next row. */
19
19
  function lastLineOf(range: Range): DOMRect | null {
20
20
  const rects = [...range.getClientRects()].filter((rect) => rect.width > 0);
21
+
21
22
  return rects.at(-1) ?? null;
22
23
  }
23
24
 
@@ -39,15 +40,19 @@ export function QuoteButton({
39
40
 
40
41
  useEffect(() => {
41
42
  let pressed = false;
43
+
42
44
  const read = (): void => {
43
45
  const selection = document.getSelection();
44
46
  const quote = taker.current === null ? null : selectedAnswerQuote(selection, thread.current);
45
47
  const box = frame.current;
46
48
  const line = quote === null || selection === null ? null : lastLineOf(selection.getRangeAt(0));
49
+
47
50
  if (quote === null || box === null || line === null) {
48
51
  setOffer(null);
52
+
49
53
  return;
50
54
  }
55
+
51
56
  const bounds = box.getBoundingClientRect();
52
57
  // The canvas may zoom the cell; the button is placed in the frame's own pixels.
53
58
  const zoom = box.offsetWidth > 0 ? bounds.width / box.offsetWidth : 1;
@@ -55,23 +60,30 @@ export function QuoteButton({
55
60
  const y = (line.bottom - bounds.top) / zoom + GAP_PX;
56
61
  setOffer({ quote: quote.text, itemId: quote.itemId, x: Math.round(x), y: Math.round(y) });
57
62
  };
63
+
58
64
  const press = (): void => {
59
65
  pressed = true;
60
66
  };
67
+
61
68
  const release = (): void => {
62
69
  pressed = false;
63
70
  read();
64
71
  };
72
+
65
73
  const change = (): void => {
66
74
  if (pressed) {
67
75
  setOffer(null);
76
+
68
77
  return;
69
78
  }
79
+
70
80
  read();
71
81
  };
82
+
72
83
  document.addEventListener('pointerdown', press, true);
73
84
  document.addEventListener('pointerup', release, true);
74
85
  document.addEventListener('selectionchange', change);
86
+
75
87
  return () => {
76
88
  document.removeEventListener('pointerdown', press, true);
77
89
  document.removeEventListener('pointerup', release, true);
@@ -31,6 +31,7 @@ export function TokenChip({ kind, value, sigil, icon, label, tone, onOpen, class
31
31
  onOpen();
32
32
  }
33
33
  };
34
+
34
35
  return (
35
36
  <span
36
37
  {...rest}
@@ -55,11 +56,13 @@ export function ReferenceChip({ id }: { id: string }) {
55
56
  const { t } = useTranslation('agent-chat');
56
57
  const { items, open } = useContext(ReferenceLookupContext);
57
58
  const item = items.find((candidate) => candidate.id === id);
59
+
58
60
  if (item === undefined) {
59
61
  return (
60
62
  <TokenChip kind="gone" icon={<Icon icon={CircleOff} size={14} className={TOKEN_ICON} />} label={t('references.gone')} tone={REFERENCE_GONE_TONE} />
61
63
  );
62
64
  }
65
+
63
66
  return (
64
67
  <TokenChip
65
68
  kind="reference"
@@ -20,6 +20,7 @@ interface ResumeCompactionDockProps {
20
20
  export function ResumeCompactionDock({ tokens, reason = 'idle', onCompact, onDismiss }: ResumeCompactionDockProps) {
21
21
  const { t } = useTranslation('agent-chat');
22
22
  const cold = reason === 'cache-expired';
23
+
23
24
  return (
24
25
  <div className="flex items-center gap-2 border-b border-border px-3.5 py-2.5">
25
26
  <Icon icon={Minimize2} size={16} className="shrink-0 text-text-faint" />
@@ -8,6 +8,7 @@ import { TIMESTAMP_FORMAT } from './timestamp-format';
8
8
  */
9
9
  export function RowTime({ at }: { at: number }) {
10
10
  useFormatLocale();
11
+
11
12
  return (
12
13
  <Tooltip label={formatDateTime(at, TIMESTAMP_FORMAT)}>
13
14
  <time