@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
@@ -54,6 +54,7 @@ export function VisualFrame({ chatId, visual, fill = false, className }: VisualF
54
54
  const scope = useChatScope();
55
55
  // The frame opens again when its page changes, so the reader keeps its identity while the visual does.
56
56
  const page = useCallback(() => attachments.read(scope.id, chatId, visual.id).then((blob) => blob.text()), [attachments, scope.id, chatId, visual.id]);
57
+
57
58
  return (
58
59
  <PageFrame
59
60
  page={page}
@@ -91,10 +92,12 @@ function PageFrame({ page, title, maxHeight, fill = false, className, initialHei
91
92
  const controllerRef = useRef<VisualViewportController | null>(null);
92
93
  const [viewportReady, setViewportReady] = useState(false);
93
94
  const [measure, width] = useMeasuredWidth();
95
+
94
96
  const setBox = (node: HTMLDivElement | null) => {
95
97
  boxRef.current = node;
96
98
  measure(node);
97
99
  };
100
+
98
101
  const theme = useVisualTheme(boxRef);
99
102
  const widthRef = useRef(width);
100
103
  const onHeightRef = useRef(onHeight);
@@ -104,9 +107,11 @@ function PageFrame({ page, title, maxHeight, fill = false, className, initialHei
104
107
  const [failed, setFailed] = useState(false);
105
108
  // The frame keeps the address it opened with: a later theme goes over the bridge, since a new address would load the page again.
106
109
  const [opening, setOpening] = useState<{ src: string; theme: VisualTheme } | null>(null);
110
+
107
111
  if (opening === null && frameUrl !== null && theme !== null && width > 0) {
108
112
  setOpening({ src: frameUrl + visualThemeFragment(theme), theme });
109
113
  }
114
+
110
115
  const height = fill || width === 0 ? undefined : (reported ?? initialHeight(width));
111
116
 
112
117
  useLayoutEffect(() => {
@@ -116,9 +121,11 @@ function PageFrame({ page, title, maxHeight, fill = false, className, initialHei
116
121
 
117
122
  useLayoutEffect(() => {
118
123
  const frame = frameRef.current;
124
+
119
125
  if (opening === null || frame === null) {
120
126
  return;
121
127
  }
128
+
122
129
  const bridge = new VisualBridge({
123
130
  frame: () => frame.contentWindow,
124
131
  visual: { maxHeight },
@@ -130,6 +137,7 @@ function PageFrame({ page, title, maxHeight, fill = false, className, initialHei
130
137
  if (fill) {
131
138
  return;
132
139
  }
140
+
133
141
  setReported(next);
134
142
  onHeightRef.current?.(widthRef.current, next);
135
143
  },
@@ -142,10 +150,14 @@ function PageFrame({ page, title, maxHeight, fill = false, className, initialHei
142
150
  onAppearance: setDrawn,
143
151
  onFailure: () => setFailed(true)
144
152
  });
153
+
145
154
  bridgeRef.current = bridge;
146
155
  const view = frame.ownerDocument.defaultView ?? window;
156
+
147
157
  const listen = (event: MessageEvent): void => bridge.receive(event);
158
+
148
159
  view.addEventListener('message', listen);
160
+
149
161
  return () => {
150
162
  view.removeEventListener('message', listen);
151
163
  bridge.dispose();
@@ -158,11 +170,14 @@ function PageFrame({ page, title, maxHeight, fill = false, className, initialHei
158
170
  const viewport = viewportRef.current;
159
171
  const bridge = bridgeRef.current;
160
172
  const scroller = box?.closest<HTMLElement>('.chat-scroll');
173
+
161
174
  if (fill || !viewportReady || !box || !viewport || !bridge || !scroller) {
162
175
  return;
163
176
  }
177
+
164
178
  const controller = new VisualViewportController(box, viewport, scroller, bridge);
165
179
  controllerRef.current = controller;
180
+
166
181
  return () => {
167
182
  controller.dispose();
168
183
  controllerRef.current = null;
@@ -13,6 +13,7 @@ export function WelcomeGreeting({ chatId, now = localClock }: { chatId: string;
13
13
  const { t } = useTranslation('agent-chat');
14
14
  const provider = useChatRow(chatId, (row) => row?.info.provider ?? null);
15
15
  const [part] = useState(() => dayPartOf(now()));
16
+
16
17
  return (
17
18
  <div className="flex items-center justify-center gap-3 px-3 pb-2 text-text">
18
19
  {provider && <AgentIcon kind={provider} size={32} className="shrink-0" />}
@@ -22,7 +22,9 @@ export function messageClipboardChips(
22
22
  if (item.kind === 'assistant') {
23
23
  return clipboardChipsOf(text, tokenizeNamedReferences(text, lookup.items, lookup.referenced));
24
24
  }
25
+
25
26
  const tokens = referenceTokens([...(item.chats ?? []), ...(item.references ?? [])], lookup.items);
27
+
26
28
  return clipboardChipsOf(text, tokenizeChips(text, item.mentions ?? [], item.skills ?? [], tokens));
27
29
  }
28
30
 
@@ -31,15 +33,18 @@ export function rowClipboardChips(row: TimelineRow, text: string, lookup: Pick<R
31
33
  if (row.kind === 'user' || row.kind === 'assistant') {
32
34
  return messageClipboardChips(row.item, text, lookup);
33
35
  }
36
+
34
37
  return clipboardChipsOf(text, []);
35
38
  }
36
39
 
37
40
  /* What a chip the transcript drew stands for, read back from the attributes `TokenChip` writes. */
38
41
  function segmentOf(chip: Element): ChipSegment | null {
39
42
  const value = chip.getAttribute('data-chip-value');
43
+
40
44
  if (value === null) {
41
45
  return null;
42
46
  }
47
+
43
48
  switch (chip.getAttribute('data-chip')) {
44
49
  case 'mention':
45
50
  return { kind: 'mention', path: value };
@@ -47,6 +52,7 @@ function segmentOf(chip: Element): ChipSegment | null {
47
52
  return { kind: 'skill', name: value };
48
53
  case 'reference': {
49
54
  const name = chip.getAttribute('data-chip-name');
55
+
50
56
  return name === null || name === '' ? null : { kind: 'reference', id: value, name };
51
57
  }
52
58
  default:
@@ -57,31 +63,39 @@ function segmentOf(chip: Element): ChipSegment | null {
57
63
  /* The chips a selection holds whole, beside the text it copies; a chip cut in half is only text. */
58
64
  export function selectionChips(selection: Selection): ClipboardChips {
59
65
  const segments: ChipSegment[] = [];
66
+
60
67
  for (let i = 0; i < selection.rangeCount; i++) {
61
68
  const container = selection.getRangeAt(i).commonAncestorContainer;
62
69
  const root = container instanceof Element ? container : container.parentElement;
63
70
  const own = root?.closest('[data-chip]');
64
71
  const candidates = own ? [own] : [...(root?.querySelectorAll('[data-chip]') ?? [])];
72
+
65
73
  for (const chip of candidates) {
66
74
  const segment = selection.containsNode(chip, false) ? segmentOf(chip) : null;
75
+
67
76
  if (segment !== null) {
68
77
  segments.push(segment);
69
78
  }
70
79
  }
71
80
  }
81
+
72
82
  return clipboardChipsOf(selection.toString(), segments);
73
83
  }
74
84
 
75
85
  /* A copy of a selection in the thread, which carries the chips in it as well when it holds any. */
76
86
  export function copySelectionWithChips(event: ClipboardEvent): void {
77
87
  const selection = document.getSelection();
88
+
78
89
  if (event.defaultPrevented || event.clipboardData === null || selection === null || selection.isCollapsed) {
79
90
  return;
80
91
  }
92
+
81
93
  const chips = selectionChips(selection);
94
+
82
95
  if (!hasClipboardChips(chips)) {
83
96
  return;
84
97
  }
98
+
85
99
  writeClipboardChips(event.clipboardData, chips);
86
100
  event.preventDefault();
87
101
  }
@@ -93,9 +107,12 @@ export function copySelectionWithChips(event: ClipboardEvent): void {
93
107
  export function copyWithChips(chips: ClipboardChips): void {
94
108
  if (!hasClipboardChips(chips) || typeof document.execCommand !== 'function') {
95
109
  void copyText(chips.text);
110
+
96
111
  return;
97
112
  }
113
+
98
114
  let written = false;
115
+
99
116
  const write = (event: ClipboardEvent): void => {
100
117
  if (event.clipboardData !== null) {
101
118
  writeClipboardChips(event.clipboardData, chips);
@@ -103,12 +120,15 @@ export function copyWithChips(chips: ClipboardChips): void {
103
120
  written = true;
104
121
  }
105
122
  };
123
+
106
124
  document.addEventListener('copy', write, true);
125
+
107
126
  try {
108
127
  document.execCommand('copy');
109
128
  } finally {
110
129
  document.removeEventListener('copy', write, true);
111
130
  }
131
+
112
132
  if (!written) {
113
133
  void copyText(chips.text);
114
134
  }
@@ -22,25 +22,32 @@ export class TokenCache<State> {
22
22
  if (state !== undefined) {
23
23
  return tokenize(code, state);
24
24
  }
25
+
25
26
  const key = `${namespace}\0${code}`;
26
27
  const known = this.entries.get(key);
28
+
27
29
  if (known !== undefined) {
28
30
  this.entries.delete(key);
29
31
  this.entries.set(key, known);
32
+
30
33
  return known.result;
31
34
  }
35
+
32
36
  const result = tokenize(code, state);
33
37
  // Account for tokens too, so dense code cannot fill the cache with tiny strings.
34
38
  const cost = key.length * 2 + result.lines.reduce((sum, line) => sum + line.length * 64, 0);
39
+
35
40
  if (cost <= TOKEN_CACHE_BUDGET) {
36
41
  while (this.entries.size > 0 && (this.size + cost > TOKEN_CACHE_BUDGET || this.entries.size >= TOKEN_CACHE_ENTRIES)) {
37
42
  const oldest = this.entries.keys().next().value!;
38
43
  this.size -= this.entries.get(oldest)!.cost;
39
44
  this.entries.delete(oldest);
40
45
  }
46
+
41
47
  this.entries.set(key, { result, cost });
42
48
  this.size += cost;
43
49
  }
50
+
44
51
  return result;
45
52
  };
46
53
  }
@@ -68,24 +75,31 @@ export class IncrementalLines<State> {
68
75
  // A carriage return splits lines in ways a cut at newlines does not see, so that code starts over every time.
69
76
  if (code.includes('\r')) {
70
77
  this.reset();
78
+
71
79
  return this.tokenize(code, undefined).lines;
72
80
  }
81
+
73
82
  const end = complete ? code.length : code.lastIndexOf('\n') + 1;
74
83
  const stable = code.slice(0, end);
75
84
  const continues = (this.source === '' || this.source.endsWith('\n')) && stable.startsWith(this.source);
85
+
76
86
  if (!continues) {
77
87
  this.reset();
78
88
  }
89
+
79
90
  const added = stable.slice(this.source.length);
91
+
80
92
  if (added !== '') {
81
93
  const result = this.tokenize(added.endsWith('\n') ? added.slice(0, -1) : added, this.state);
82
94
  this.lines = [...this.lines, ...result.lines];
83
95
  this.state = result.state;
84
96
  this.source = stable;
85
97
  }
98
+
86
99
  if (complete) {
87
100
  return this.lines;
88
101
  }
102
+
89
103
  return [...this.lines, ...this.tokenize(code.slice(end), this.state).lines];
90
104
  }
91
105
 
@@ -6,6 +6,7 @@ import { useHostCodeMode, useHostCodeThemes } from '../../host-hooks';
6
6
  export function useCodeTheme(): string {
7
7
  const mode = useHostCodeMode();
8
8
  const themes = useHostCodeThemes();
9
+
9
10
  return mode === 'dark' ? themes.dark : themes.light;
10
11
  }
11
12
 
@@ -40,6 +40,7 @@ function SendButton({ send, writable }: SendButtonProps) {
40
40
  const { t } = useTranslation('agent-chat');
41
41
  const [box, setBox] = useState<HTMLDivElement | null>(null);
42
42
  const split = send.actions.length > 0;
43
+
43
44
  const button = (
44
45
  <Tooltip label={send.label} kbd={send.kbd} name>
45
46
  <button
@@ -54,10 +55,13 @@ function SendButton({ send, writable }: SendButtonProps) {
54
55
  </button>
55
56
  </Tooltip>
56
57
  );
58
+
57
59
  if (!split) {
58
60
  return button;
59
61
  }
62
+
60
63
  const { draft } = send;
64
+
61
65
  return (
62
66
  // One round shape that clips both halves, the way a split button of the library draws its frame.
63
67
  <div ref={setBox} className="flex h-9 shrink-0 overflow-clip rounded-full">
@@ -122,6 +126,7 @@ export function ComposerControls({
122
126
  const model = providers.find((entry) => entry.kind === info.provider)?.models.find((entry) => entry.slug === info.selection.model);
123
127
  // The CLI announces its session on the first message, so anything before that is still a blank chat.
124
128
  const started = info.agentSessionId !== null || info.usage.turns > 0;
129
+
125
130
  // A chat that has started keeps its provider (the CLI holds the thread), and so does one opened for a
126
131
  // named CLI; its models stay switchable, since the CLI takes a new one on the restart the next send does.
127
132
  const pickable =
@@ -9,6 +9,7 @@ import { optionIdOf } from './menu-aria';
9
9
 
10
10
  function splitPath(path: string): { name: string; dir: string } {
11
11
  const slash = path.lastIndexOf('/');
12
+
12
13
  return slash < 0 ? { name: path, dir: '' } : { name: path.slice(slash + 1), dir: path.slice(0, slash) };
13
14
  }
14
15
 
@@ -41,6 +42,7 @@ function MenuOption({ menus, index, className, onPick, children }: OptionProps)
41
42
 
42
43
  function CommandMenu({ menus }: { menus: Menus }) {
43
44
  const { t } = useTranslation('agent-chat');
45
+
44
46
  return (
45
47
  <div id={menus.listId} role="listbox" aria-label={t('composer.menu.commands')} className="border-b border-border px-1.5 py-1.5">
46
48
  {menus.commands.map((command, index) => (
@@ -56,6 +58,7 @@ function CommandMenu({ menus }: { menus: Menus }) {
56
58
 
57
59
  function SkillMenu({ menus }: { menus: Menus }) {
58
60
  const { t } = useTranslation('agent-chat');
61
+
59
62
  return (
60
63
  <div id={menus.listId} role="listbox" aria-label={t('composer.menu.skills')} className="border-b border-border px-1.5 py-1.5">
61
64
  {menus.skillMatches.map((skill, index) => (
@@ -76,6 +79,7 @@ function MentionMenu({ menus, query }: { menus: Menus; query: string }) {
76
79
  const { listId, files, mentionCount } = menus;
77
80
  const filesLabelId = `${listId}-files`;
78
81
  const filesLabelShown = query === '';
82
+
79
83
  return (
80
84
  <div className="border-b border-border px-1.5 py-1.5">
81
85
  {mentionCount > 0 && (
@@ -83,6 +87,7 @@ function MentionMenu({ menus, query }: { menus: Menus; query: string }) {
83
87
  {filesLabelShown && <Menu.Label id={filesLabelId}>{t('composer.mentions.here')}</Menu.Label>}
84
88
  {files.map((path, index) => {
85
89
  const { name, dir } = splitPath(path);
90
+
86
91
  return (
87
92
  <MenuOption key={path} menus={menus} index={index} className="items-center" onPick={() => menus.chooseMention(path)}>
88
93
  <FileIcon path={path} size={14} />
@@ -104,9 +109,11 @@ function MentionMenu({ menus, query }: { menus: Menus; query: string }) {
104
109
  function ReferenceMenu({ menus }: { menus: Menus }) {
105
110
  const { t } = useTranslation('agent-chat');
106
111
  const { listId, referenceMatches } = menus;
112
+
107
113
  const groups = (['canvas', 'views'] as const)
108
114
  .map((group) => ({ group, items: referenceMatches.filter((item) => item.group === group) }))
109
115
  .filter((entry) => entry.items.length > 0);
116
+
110
117
  return (
111
118
  <div className="border-b border-border px-1.5 py-1.5">
112
119
  {referenceMatches.length > 0 ? (
@@ -116,6 +123,7 @@ function ReferenceMenu({ menus }: { menus: Menus }) {
116
123
  <Menu.Label id={`${listId}-${group}`}>{t(`composer.references.${group}`)}</Menu.Label>
117
124
  {items.map((item) => {
118
125
  const index = referenceMatches.indexOf(item);
126
+
119
127
  return (
120
128
  <MenuOption key={item.id} menus={menus} index={index} className="items-center" onPick={() => menus.chooseReference(item)}>
121
129
  <Icon icon={REFERENCE_ICONS[item.kind]} size={14} className="shrink-0 text-text-faint" />
@@ -141,14 +149,18 @@ export function ComposerMenu({ menus }: { menus: Menus }) {
141
149
  if (menus.commandMenuOpen) {
142
150
  return <CommandMenu menus={menus} />;
143
151
  }
152
+
144
153
  if (menus.skillMenuOpen) {
145
154
  return <SkillMenu menus={menus} />;
146
155
  }
156
+
147
157
  if (menus.mentionMenuOpen && menus.mention !== null) {
148
158
  return <MentionMenu menus={menus} query={menus.mention.query} />;
149
159
  }
160
+
150
161
  if (menus.referenceMenuOpen) {
151
162
  return <ReferenceMenu menus={menus} />;
152
163
  }
164
+
153
165
  return null;
154
166
  }
@@ -22,6 +22,7 @@ interface DraftQuoteProps {
22
22
  export function DraftQuote({ chatId, quote, source, onRemove, onSourceGone }: DraftQuoteProps) {
23
23
  const { t } = useTranslation('agent-chat');
24
24
  const scope = useChatScope();
25
+
25
26
  return (
26
27
  <div className={`flex items-center gap-2 ${ROW}`}>
27
28
  {source === null ? (
@@ -45,6 +46,7 @@ export function DraftQuote({ chatId, quote, source, onRemove, onSourceGone }: Dr
45
46
  /* The files the message carries: an image as a thumbnail, anything else by its name and size. */
46
47
  export function DraftAttachments({ attachments, onRemove }: { attachments: readonly ChatAttachmentUpload[]; onRemove(index: number): void }) {
47
48
  const { t } = useTranslation('agent-chat');
49
+
48
50
  return (
49
51
  <div className={`flex flex-wrap gap-2 ${ROW}`}>
50
52
  {attachments.map((attachment, index) => (
@@ -18,6 +18,7 @@ interface QueuedMessagesProps {
18
18
  export function QueuedMessages({ chatId, queue, paused, takingBack, onEdit }: QueuedMessagesProps) {
19
19
  const { t } = useTranslation('agent-chat');
20
20
  const actions = useChatActions();
21
+
21
22
  return (
22
23
  <div className="flex flex-col gap-1 border-b border-border px-2 py-1.5">
23
24
  {paused && <p className="px-1.5 text-xs text-text-muted">{t('composer.queue.paused')}</p>}
@@ -26,8 +27,11 @@ export function QueuedMessages({ chatId, queue, paused, takingBack, onEdit }: Qu
26
27
  scrollTimelineToEnd(chatId, 'instant');
27
28
  void actions.sendNow(chatId, message.id).catch(() => undefined);
28
29
  };
30
+
29
31
  const unqueue = (): void => void actions.unqueue(chatId, message.id).catch(() => undefined);
32
+
30
33
  const edit = (): void => onEdit(message);
34
+
31
35
  return (
32
36
  <ContextMenu.Root key={message.id}>
33
37
  <ContextMenu.Trigger className="group/queued flex items-center gap-2 rounded-md px-1.5 py-1 text-xs text-text-muted">
@@ -32,6 +32,7 @@ export function pastedChipsIn(text: string, from: number, to: number, code: read
32
32
  const outside = [...code, { from: 0, to: from }, { from: to, to: text.length }];
33
33
  const skills = chipRanges(text, [], [...choices.skills], outside);
34
34
  const references = namedReferenceRanges(text, choices.referables, choices.referenced, outside);
35
+
35
36
  return clipboardChipsOf(
36
37
  text.slice(from, to),
37
38
  [...skills, ...references].map((range) =>
@@ -51,9 +52,12 @@ export function withPastedChips(draft: ChatDraft, chips: ClipboardChips, choices
51
52
  ...ids,
52
53
  ...more.filter((id, index) => !ids.includes(id) && more.indexOf(id) === index)
53
54
  ];
55
+
54
56
  const isChat = (token: ReferenceToken): boolean => choices.referables.some((item) => item.id === token.id && item.kind === 'chat');
57
+
55
58
  const chats = chips.references.filter(isChat);
56
59
  const references = chips.references.filter((token) => !isChat(token));
60
+
57
61
  return {
58
62
  ...draft,
59
63
  mentions: choices.mentions ? add(draft.mentions, chips.mentions) : draft.mentions,
@@ -80,20 +84,26 @@ export function withPastedChips(draft: ChatDraft, chips: ClipboardChips, choices
80
84
  export function pasteWithChips(view: EditorView, clip: string, copied: ClipboardChips | null, choices: PasteChoices): ClipboardChips | null {
81
85
  if (copied !== null && hasClipboardChips(copied)) {
82
86
  view.dispatch(view.state.replaceSelection(copied.text), { scrollIntoView: true, userEvent: 'input.paste' });
87
+
83
88
  return copied;
84
89
  }
90
+
85
91
  if (!mayHoldChips(clip)) {
86
92
  return null;
87
93
  }
94
+
88
95
  const from = view.state.selection.main.from;
89
96
  const inserted = view.state.replaceSelection(clip);
90
97
  const state = view.state.update(inserted).state;
91
98
  const to = state.selection.main.head;
92
99
  const found = pastedChipsIn(state.doc.toString(), from, to, codeRanges(state), choices);
100
+
93
101
  if (!hasClipboardChips(found)) {
94
102
  return null;
95
103
  }
104
+
96
105
  view.dispatch(inserted, { scrollIntoView: true, userEvent: 'input.paste' });
106
+
97
107
  return found;
98
108
  }
99
109
 
@@ -105,18 +115,24 @@ export function copyWithChipsFromEditor(
105
115
  references: readonly ReferenceToken[]
106
116
  ): boolean {
107
117
  const { from, to } = view.state.selection.main;
118
+
108
119
  if (from === to || event.clipboardData === null) {
109
120
  return false;
110
121
  }
122
+
111
123
  const text = view.state.sliceDoc(from, to);
112
124
  const chips = clipboardChipsOf(text, tokenizeChips(text, draft.mentions, draft.skills, references));
125
+
113
126
  if (!hasClipboardChips(chips)) {
114
127
  return false;
115
128
  }
129
+
116
130
  writeClipboardChips(event.clipboardData, chips);
117
131
  event.preventDefault();
132
+
118
133
  if (event.type === 'cut') {
119
134
  view.dispatch({ changes: { from, to }, scrollIntoView: true, userEvent: 'delete.cut' });
120
135
  }
136
+
121
137
  return true;
122
138
  }
@@ -47,6 +47,7 @@ class ReferenceIcon extends WidgetType {
47
47
  const root = createRoot(holder);
48
48
  root.render(createElement(Icon, { icon: REFERENCE_ICONS[this.kind], size: 12 }));
49
49
  roots.set(holder, root);
50
+
50
51
  return holder;
51
52
  }
52
53
 
@@ -62,18 +63,23 @@ function chipMarks(state: EditorState): DecorationSet {
62
63
  const { mentions, skills, references = [] } = state.facet(choices);
63
64
  const kinds = new Map(references.map((token) => [token.id, token.kind]));
64
65
  const marks: Range<Decoration>[] = [];
66
+
65
67
  // `@a.ts` in backticks is code, not a mention.
66
68
  for (const range of chipRanges(state.doc.toString(), mentions, skills, codeRanges(state), references)) {
67
69
  if (range.kind !== 'reference') {
68
70
  marks.push((range.kind === 'skill' ? skillMark : mentionMark).range(range.from, range.to));
71
+
69
72
  continue;
70
73
  }
74
+
71
75
  marks.push(referenceMark.range(range.from, range.to));
72
76
  const kind = kinds.get(range.value);
77
+
73
78
  if (kind !== undefined && kind !== null) {
74
79
  marks.push(Decoration.replace({ widget: new ReferenceIcon(kind) }).range(range.from, range.from + 1));
75
80
  }
76
81
  }
82
+
77
83
  return Decoration.set(marks, true);
78
84
  }
79
85
 
@@ -33,17 +33,21 @@ export interface CodeRange {
33
33
  /* The code spans and fenced blocks between `from` and `to`, as far as the parser has got. */
34
34
  export function codeRanges(state: EditorState, from = 0, to = state.doc.length): CodeRange[] {
35
35
  const found: CodeRange[] = [];
36
+
36
37
  syntaxTree(state).iterate({
37
38
  from,
38
39
  to,
39
40
  enter: (node) => {
40
41
  if (node.name === 'InlineCode' || node.name === 'FencedCode') {
41
42
  found.push({ from: node.from, to: node.to, kind: node.name === 'InlineCode' ? 'inline' : 'fence' });
43
+
42
44
  return false;
43
45
  }
46
+
44
47
  return undefined;
45
48
  }
46
49
  });
50
+
47
51
  return found;
48
52
  }
49
53
 
@@ -61,16 +65,20 @@ function codeDecorations(view: EditorView): DecorationSet {
61
65
  const { state } = view;
62
66
  const decorations: Array<Range<Decoration>> = [];
63
67
  const fenceLines = new Set<number>();
68
+
64
69
  for (const visible of view.visibleRanges) {
65
70
  for (const range of codeRanges(state, visible.from, visible.to)) {
66
71
  if (range.kind === 'inline') {
67
72
  decorations.push(inlineCodeMark.range(range.from, range.to));
73
+
68
74
  continue;
69
75
  }
76
+
70
77
  const first = state.doc.lineAt(range.from).number;
71
78
  const last = state.doc.lineAt(range.to).number;
72
79
  const start = state.doc.lineAt(Math.max(range.from, visible.from)).number;
73
80
  const end = state.doc.lineAt(Math.min(range.to, visible.to)).number;
81
+
74
82
  for (let number = start; number <= end; number++) {
75
83
  if (!fenceLines.has(number)) {
76
84
  fenceLines.add(number);
@@ -79,6 +87,7 @@ function codeDecorations(view: EditorView): DecorationSet {
79
87
  }
80
88
  }
81
89
  }
90
+
82
91
  return Decoration.set(decorations, true);
83
92
  }
84
93
 
@@ -12,11 +12,13 @@ export function insertAtSelection(state: EditorState, text: string): Transaction
12
12
  if (text === '') {
13
13
  return {};
14
14
  }
15
+
15
16
  const { from, to } = state.selection.main;
16
17
  const before = state.doc.sliceString(Math.max(0, from - 1), from);
17
18
  const after = state.doc.sliceString(to, to + 1);
18
19
  const lead = before !== '' && !isSpace(before) && !isSpace(text) ? ' ' : '';
19
20
  const trail = (after === '' || !isSpace(after)) && !/\s$/.test(text) ? ' ' : '';
20
21
  const insert = `${lead}${text}${trail}`;
22
+
21
23
  return { changes: { from, to, insert }, selection: { anchor: from + insert.length }, scrollIntoView: true, userEvent: 'input' };
22
24
  }