@adecore/agents-react 0.39.0 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/dist/agents/AgentModelLabel.js +2 -1
  2. package/dist/agents/model-name.js +2 -1
  3. package/dist/agents/provider-paths.d.ts +2 -1
  4. package/dist/agents/provider-paths.js +7 -3
  5. package/dist/chat/account-choice.js +57 -28
  6. package/dist/chat/bookmarks.js +2 -1
  7. package/dist/chat/drafts.d.ts +0 -1
  8. package/dist/chat/drafts.js +9 -28
  9. package/dist/chat/logic/picker-entries.js +2 -8
  10. package/dist/chat/logic/resume-compaction.js +2 -5
  11. package/dist/chat/logic/timeline-copy.js +2 -8
  12. package/dist/chat/logic/timeline.js +9 -10
  13. package/dist/chat/logic/workflow.js +2 -4
  14. package/dist/chat/subagent-conversation.js +2 -1
  15. package/dist/chat/ui/GoalDock.js +2 -1
  16. package/dist/chat/ui/LimitState.js +4 -6
  17. package/dist/chat/ui/composer/use-continuable-turn.js +0 -1
  18. package/dist/chat/ui/composer/use-notice.js +2 -1
  19. package/dist/chat/ui/composer/use-prompt-history.js +1 -1
  20. package/dist/chat/ui/composer/use-prompt-menus.js +216 -253
  21. package/dist/chat/ui/composer/use-take-back.js +2 -2
  22. package/dist/chat/ui/diff/ApprovalReview.js +1 -1
  23. package/dist/chat/ui/highlighter.d.ts +0 -6
  24. package/dist/chat/ui/highlighter.js +0 -12
  25. package/dist/chat/ui/icons.d.ts +0 -1
  26. package/dist/chat/ui/icons.js +0 -3
  27. package/dist/chat/ui/rows/MessageRows.js +2 -5
  28. package/dist/chat/ui/rows/row-rhythm.js +2 -5
  29. package/dist/chat/ui/theme-watch.js +3 -7
  30. package/dist/prompts/logic/usePromptSession.js +2 -1
  31. package/dist/providers/AddAccountForm.js +2 -1
  32. package/dist/providers/CliDetail.js +104 -130
  33. package/dist/providers/account-actions.js +6 -9
  34. package/dist/state/chats.d.ts +1 -0
  35. package/dist/state/chats.js +6 -11
  36. package/dist/state/provider-accounts.d.ts +1 -1
  37. package/dist/state/provider-accounts.js +2 -8
  38. package/dist/transport.d.ts +2 -0
  39. package/dist/transport.js +14 -0
  40. package/dist/usage/ChartLogo.d.ts +5 -0
  41. package/dist/usage/ChartLogo.js +48 -0
  42. package/dist/usage/LimitsList.js +6 -16
  43. package/dist/usage/UsageBreakdown.js +17 -40
  44. package/dist/usage/UsageLimits.js +183 -211
  45. package/dist/usage/UsagePage.js +3 -17
  46. package/dist/usage/UsageSummary.js +4 -9
  47. package/dist/usage/limits.js +16 -31
  48. package/dist/usage/summary.d.ts +1 -1
  49. package/dist/usage/summary.js +3 -5
  50. package/package.json +27 -27
  51. package/src/agents/AccountDot.tsx +1 -0
  52. package/src/agents/AgentIcon.tsx +2 -0
  53. package/src/agents/AgentModelLabel.tsx +4 -6
  54. package/src/agents/ModelOptionControl.tsx +8 -0
  55. package/src/agents/account-limits.ts +3 -0
  56. package/src/agents/accounts.ts +12 -0
  57. package/src/agents/model-name.ts +13 -1
  58. package/src/agents/model-options.ts +2 -0
  59. package/src/agents/model-requirement.ts +4 -0
  60. package/src/agents/option-words.ts +2 -0
  61. package/src/agents/provider-paths.ts +8 -3
  62. package/src/agents/status-look.ts +1 -0
  63. package/src/chat/account-choice.ts +25 -19
  64. package/src/chat/actions.ts +5 -0
  65. package/src/chat/attachments.ts +8 -0
  66. package/src/chat/bookmarks.ts +13 -1
  67. package/src/chat/chat-client.ts +63 -0
  68. package/src/chat/chat-references.ts +3 -0
  69. package/src/chat/composer-effort.ts +5 -0
  70. package/src/chat/composer-send.ts +4 -0
  71. package/src/chat/drafts.ts +21 -17
  72. package/src/chat/forks.ts +4 -0
  73. package/src/chat/guards.ts +2 -0
  74. package/src/chat/index.ts +7 -0
  75. package/src/chat/logic/ansi.ts +47 -0
  76. package/src/chat/logic/bookmarks.ts +7 -0
  77. package/src/chat/logic/command-looks.ts +29 -0
  78. package/src/chat/logic/context-usage.ts +3 -0
  79. package/src/chat/logic/continue-after-stop.ts +5 -0
  80. package/src/chat/logic/effort.ts +4 -0
  81. package/src/chat/logic/embedded-code.ts +63 -0
  82. package/src/chat/logic/fork.ts +22 -0
  83. package/src/chat/logic/generated-image.ts +7 -0
  84. package/src/chat/logic/goal.ts +7 -0
  85. package/src/chat/logic/handback.ts +2 -0
  86. package/src/chat/logic/limit.ts +8 -0
  87. package/src/chat/logic/picker-entries.ts +16 -4
  88. package/src/chat/logic/resume-compaction.ts +22 -5
  89. package/src/chat/logic/scrubber.ts +38 -5
  90. package/src/chat/logic/thought.ts +5 -0
  91. package/src/chat/logic/thread-cards.ts +6 -0
  92. package/src/chat/logic/timeline-copy.ts +4 -9
  93. package/src/chat/logic/timeline-target.ts +15 -0
  94. package/src/chat/logic/timeline.ts +113 -10
  95. package/src/chat/logic/tools.ts +30 -0
  96. package/src/chat/logic/visual-height.ts +5 -0
  97. package/src/chat/logic/visual-viewport.ts +1 -0
  98. package/src/chat/logic/welcome.ts +3 -0
  99. package/src/chat/logic/workflow.ts +26 -7
  100. package/src/chat/mentions.ts +56 -0
  101. package/src/chat/pending-prompts.ts +16 -0
  102. package/src/chat/persisted-json.ts +15 -0
  103. package/src/chat/preferences.ts +16 -0
  104. package/src/chat/prompt-hold.ts +2 -0
  105. package/src/chat/quote.ts +7 -0
  106. package/src/chat/reading-width.ts +1 -0
  107. package/src/chat/recent-messages.ts +5 -0
  108. package/src/chat/resume-compaction-dismissals.ts +1 -0
  109. package/src/chat/stash.ts +12 -0
  110. package/src/chat/subagent-conversation.ts +24 -4
  111. package/src/chat/subagent-list.ts +14 -0
  112. package/src/chat/subagent-support.ts +1 -0
  113. package/src/chat/subagent-view.ts +9 -0
  114. package/src/chat/thumbnails.ts +14 -0
  115. package/src/chat/timeline-flash.ts +2 -0
  116. package/src/chat/timeline-scroll.ts +23 -0
  117. package/src/chat/ui/AccountPill.tsx +4 -0
  118. package/src/chat/ui/AnsiOutput.tsx +1 -0
  119. package/src/chat/ui/BookmarkMarker.tsx +5 -0
  120. package/src/chat/ui/BookmarkSubmenu.tsx +1 -0
  121. package/src/chat/ui/ChatActivity.tsx +13 -0
  122. package/src/chat/ui/CodeBlock.tsx +8 -0
  123. package/src/chat/ui/Composer.tsx +15 -0
  124. package/src/chat/ui/ComposerInput.tsx +22 -0
  125. package/src/chat/ui/DiffPool.tsx +3 -0
  126. package/src/chat/ui/EditDiff.tsx +5 -0
  127. package/src/chat/ui/GoalDock.tsx +13 -1
  128. package/src/chat/ui/ImageView.tsx +11 -0
  129. package/src/chat/ui/LimitState.tsx +23 -6
  130. package/src/chat/ui/Markdown.tsx +41 -0
  131. package/src/chat/ui/MermaidBlock.tsx +16 -0
  132. package/src/chat/ui/MessageActions.tsx +8 -0
  133. package/src/chat/ui/ModelMarks.tsx +1 -0
  134. package/src/chat/ui/Pickers.tsx +26 -0
  135. package/src/chat/ui/PromptComposer.tsx +2 -0
  136. package/src/chat/ui/PromptEditor.tsx +6 -0
  137. package/src/chat/ui/QuoteButton.tsx +12 -0
  138. package/src/chat/ui/ReferenceChip.tsx +3 -0
  139. package/src/chat/ui/ResumeCompactionDock.tsx +1 -0
  140. package/src/chat/ui/RowTime.tsx +1 -0
  141. package/src/chat/ui/RunSettings.tsx +38 -0
  142. package/src/chat/ui/Scrubber.tsx +14 -0
  143. package/src/chat/ui/SessionLine.tsx +1 -0
  144. package/src/chat/ui/ShellCommand.tsx +32 -0
  145. package/src/chat/ui/SubagentControls.tsx +7 -0
  146. package/src/chat/ui/SubagentInfo.tsx +1 -0
  147. package/src/chat/ui/SubagentStopButton.tsx +6 -0
  148. package/src/chat/ui/SubagentTimeline.tsx +10 -0
  149. package/src/chat/ui/Timeline.tsx +119 -0
  150. package/src/chat/ui/TimelineMenu.tsx +5 -0
  151. package/src/chat/ui/UnifiedDiff.tsx +6 -0
  152. package/src/chat/ui/UploadThumb.tsx +2 -0
  153. package/src/chat/ui/VisualDialogs.tsx +3 -0
  154. package/src/chat/ui/VisualFrame.tsx +15 -0
  155. package/src/chat/ui/Welcome.tsx +1 -0
  156. package/src/chat/ui/chip-clipboard.ts +20 -0
  157. package/src/chat/ui/code-lines.ts +14 -0
  158. package/src/chat/ui/code-theme.ts +1 -0
  159. package/src/chat/ui/composer/ComposerControls.tsx +5 -0
  160. package/src/chat/ui/composer/ComposerMenu.tsx +12 -0
  161. package/src/chat/ui/composer/DraftParts.tsx +2 -0
  162. package/src/chat/ui/composer/QueuedMessages.tsx +4 -0
  163. package/src/chat/ui/composer/chip-paste.ts +16 -0
  164. package/src/chat/ui/composer/chips.ts +6 -0
  165. package/src/chat/ui/composer/editor.ts +9 -0
  166. package/src/chat/ui/composer/insert.ts +2 -0
  167. package/src/chat/ui/composer/key-handler.ts +39 -0
  168. package/src/chat/ui/composer/keys.ts +23 -0
  169. package/src/chat/ui/composer/placeholder.ts +6 -0
  170. package/src/chat/ui/composer/prompt-picks.ts +1 -0
  171. package/src/chat/ui/composer/surface.ts +5 -0
  172. package/src/chat/ui/composer/use-chat-controls.ts +13 -0
  173. package/src/chat/ui/composer/use-compaction-offer.ts +3 -0
  174. package/src/chat/ui/composer/use-composer-menus.ts +2 -0
  175. package/src/chat/ui/composer/use-composer-submit.ts +27 -0
  176. package/src/chat/ui/composer/use-continuable-turn.ts +3 -5
  177. package/src/chat/ui/composer/use-draft-files.ts +24 -0
  178. package/src/chat/ui/composer/use-effort-stepper.ts +8 -0
  179. package/src/chat/ui/composer/use-notice.ts +4 -1
  180. package/src/chat/ui/composer/use-pending-requests.ts +3 -0
  181. package/src/chat/ui/composer/use-prompt-history.ts +20 -11
  182. package/src/chat/ui/composer/use-prompt-hold.ts +3 -0
  183. package/src/chat/ui/composer/use-prompt-menus.ts +34 -14
  184. package/src/chat/ui/composer/use-take-back.ts +20 -2
  185. package/src/chat/ui/diff/ApprovalReview.tsx +18 -15
  186. package/src/chat/ui/diff/DiffFallbackBoundary.tsx +1 -0
  187. package/src/chat/ui/diff/DiffOmitted.tsx +1 -0
  188. package/src/chat/ui/diff/DiffStack.tsx +2 -0
  189. package/src/chat/ui/diff/DiffUnavailable.tsx +1 -0
  190. package/src/chat/ui/diff/EditorEditDiff.tsx +13 -0
  191. package/src/chat/ui/diff/EditorPatchDiff.tsx +4 -0
  192. package/src/chat/ui/diff/LegacyDiffPool.tsx +2 -0
  193. package/src/chat/ui/diff/LegacyEditDiff.tsx +2 -0
  194. package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +8 -0
  195. package/src/chat/ui/diff/RawDiff.tsx +1 -0
  196. package/src/chat/ui/diff/ReviewDiff.tsx +1 -0
  197. package/src/chat/ui/diff/diff-source-budget.ts +8 -0
  198. package/src/chat/ui/diff/editor-edit-model.ts +4 -0
  199. package/src/chat/ui/diff/review-plan.ts +9 -0
  200. package/src/chat/ui/diff/review-settle.ts +1 -0
  201. package/src/chat/ui/diff/syntax-runtime.ts +52 -0
  202. package/src/chat/ui/diff/use-review-stack.ts +9 -0
  203. package/src/chat/ui/diff-syntax.ts +12 -0
  204. package/src/chat/ui/diff-theme.ts +2 -0
  205. package/src/chat/ui/file-links.ts +2 -0
  206. package/src/chat/ui/find-reveal.ts +1 -0
  207. package/src/chat/ui/full-diff.ts +1 -0
  208. package/src/chat/ui/highlighter.ts +29 -15
  209. package/src/chat/ui/icons.tsx +2 -4
  210. package/src/chat/ui/index.ts +4 -0
  211. package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +25 -0
  212. package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +1 -0
  213. package/src/chat/ui/intelligent-ui/UiReply.tsx +63 -0
  214. package/src/chat/ui/intelligent-ui/block-local.ts +5 -0
  215. package/src/chat/ui/intelligent-ui/chart-data.ts +15 -0
  216. package/src/chat/ui/intelligent-ui/node-text.ts +17 -0
  217. package/src/chat/ui/intelligent-ui/renderers/binding.ts +1 -0
  218. package/src/chat/ui/intelligent-ui/renderers/chart.tsx +31 -0
  219. package/src/chat/ui/intelligent-ui/renderers/choices.tsx +4 -0
  220. package/src/chat/ui/intelligent-ui/renderers/content.tsx +18 -0
  221. package/src/chat/ui/intelligent-ui/renderers/data.tsx +11 -0
  222. package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +13 -0
  223. package/src/chat/ui/intelligent-ui/renderers/links.tsx +9 -0
  224. package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +15 -0
  225. package/src/chat/ui/intelligent-ui/renderers/map-style.ts +15 -0
  226. package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +48 -0
  227. package/src/chat/ui/intelligent-ui/renderers/map.tsx +20 -0
  228. package/src/chat/ui/intelligent-ui/renderers/place-point.ts +3 -0
  229. package/src/chat/ui/intelligent-ui/renderers/structure.tsx +3 -0
  230. package/src/chat/ui/intelligent-ui/renderers/text.tsx +8 -0
  231. package/src/chat/ui/intelligent-ui/reply-parts.ts +5 -0
  232. package/src/chat/ui/intelligent-ui/table-data.ts +17 -0
  233. package/src/chat/ui/intelligent-ui/use-in-view.ts +4 -0
  234. package/src/chat/ui/intelligent-ui/use-later.ts +4 -0
  235. package/src/chat/ui/intelligent-ui/use-truncated.ts +5 -0
  236. package/src/chat/ui/intelligent-ui/use-ui-links.ts +29 -0
  237. package/src/chat/ui/intelligent-ui/use-ui-queries.ts +60 -0
  238. package/src/chat/ui/markdown-blocks.ts +64 -0
  239. package/src/chat/ui/mermaid.ts +4 -0
  240. package/src/chat/ui/quote-selection.ts +10 -0
  241. package/src/chat/ui/rehype-chips.ts +5 -0
  242. package/src/chat/ui/rehype-fade.ts +2 -0
  243. package/src/chat/ui/remark-html-as-text.ts +5 -0
  244. package/src/chat/ui/reveal.ts +18 -0
  245. package/src/chat/ui/rows/ForksRow.tsx +5 -0
  246. package/src/chat/ui/rows/GeneratedImageRow.tsx +10 -0
  247. package/src/chat/ui/rows/MessageRows.tsx +29 -5
  248. package/src/chat/ui/rows/RowContainer.tsx +1 -0
  249. package/src/chat/ui/rows/Rows.tsx +2 -0
  250. package/src/chat/ui/rows/SubagentRow.tsx +13 -0
  251. package/src/chat/ui/rows/TaskRow.tsx +7 -0
  252. package/src/chat/ui/rows/VisualRow.tsx +4 -0
  253. package/src/chat/ui/rows/WorkRows.tsx +46 -0
  254. package/src/chat/ui/rows/WorkflowRow.tsx +10 -0
  255. package/src/chat/ui/rows/row-rhythm.ts +7 -5
  256. package/src/chat/ui/theme-watch.ts +8 -7
  257. package/src/chat/ui/thread-references.ts +7 -0
  258. package/src/chat/ui/use-diff-source-tokens.ts +8 -0
  259. package/src/chat/ui/useToggleSet.ts +3 -0
  260. package/src/chat/ui/visual-theme.ts +30 -0
  261. package/src/chat/visual-bridge.ts +23 -0
  262. package/src/chat/visual-viewport.ts +12 -0
  263. package/src/host-hooks.ts +6 -4
  264. package/src/host.ts +2 -0
  265. package/src/index.ts +3 -0
  266. package/src/lazy.tsx +9 -0
  267. package/src/mounted-registry.ts +4 -0
  268. package/src/port-transport.ts +32 -0
  269. package/src/prompts/index.ts +2 -0
  270. package/src/prompts/logic/focus.ts +5 -0
  271. package/src/prompts/logic/keys.ts +12 -0
  272. package/src/prompts/logic/prompts.ts +8 -0
  273. package/src/prompts/logic/subjects.ts +8 -0
  274. package/src/prompts/logic/useFocusAfterAnswer.ts +2 -0
  275. package/src/prompts/logic/usePromptSession.ts +13 -1
  276. package/src/prompts/ui/ApprovalBody.tsx +2 -0
  277. package/src/prompts/ui/PromptActions.tsx +2 -0
  278. package/src/prompts/ui/PromptCard.tsx +6 -0
  279. package/src/prompts/ui/PromptView.tsx +6 -0
  280. package/src/prompts/ui/QuestionBody.tsx +11 -0
  281. package/src/providers/AccountColors.tsx +1 -0
  282. package/src/providers/AccountDetail.tsx +7 -0
  283. package/src/providers/AccountVariables.tsx +2 -0
  284. package/src/providers/AddAccountForm.tsx +5 -1
  285. package/src/providers/CliDetail.tsx +6 -5
  286. package/src/providers/InstallPage.tsx +11 -0
  287. package/src/providers/ProviderList.tsx +3 -0
  288. package/src/providers/ProvidersPane.tsx +18 -0
  289. package/src/providers/account-actions.ts +10 -8
  290. package/src/providers/install-steps.ts +3 -0
  291. package/src/providers/pane-target.ts +5 -0
  292. package/src/providers/provider-abilities.ts +1 -0
  293. package/src/providers/provider-groups.ts +6 -0
  294. package/src/providers/variables.ts +12 -0
  295. package/src/scope.ts +2 -0
  296. package/src/state/chats.ts +61 -14
  297. package/src/state/index.ts +2 -0
  298. package/src/state/provider-accounts.ts +7 -9
  299. package/src/state/providers.ts +1 -0
  300. package/src/state/usage.ts +6 -0
  301. package/src/storage.ts +18 -0
  302. package/src/testing/prompts.ts +5 -0
  303. package/src/transport.ts +18 -0
  304. package/src/usage/ChartLogo.tsx +12 -0
  305. package/src/usage/LimitsList.tsx +7 -8
  306. package/src/usage/UsageBreakdown.tsx +11 -4
  307. package/src/usage/UsageChart.tsx +5 -0
  308. package/src/usage/UsageLimits.tsx +10 -7
  309. package/src/usage/UsageLimitsCard.tsx +2 -0
  310. package/src/usage/UsagePage.tsx +16 -19
  311. package/src/usage/UsageSummary.tsx +4 -5
  312. package/src/usage/UsageTiles.tsx +2 -0
  313. package/src/usage/format.ts +7 -0
  314. package/src/usage/limit-groups.ts +14 -0
  315. package/src/usage/limits.ts +11 -13
  316. package/src/usage/money.ts +1 -0
  317. package/src/usage/summary.ts +15 -7
@@ -54,9 +54,11 @@ export function useDraftFiles({
54
54
  /* Paths dragged in from a list of files. With no mention query to insert into, they land at the end of the prompt. */
55
55
  const addMentions = (paths: string[]): void => {
56
56
  const fresh = paths.filter((path) => path !== '');
57
+
57
58
  if (fresh.length === 0 || capabilities?.mentions === false) {
58
59
  return;
59
60
  }
61
+
60
62
  const appended = fresh.map((path) => `@${path}`).join(' ');
61
63
  const before = draft.text.replace(/\s+$/, '');
62
64
  setText(before === '' ? `${appended} ` : `${before} ${appended} `, [...draft.mentions, ...fresh.filter((path) => !draft.mentions.includes(path))]);
@@ -66,21 +68,27 @@ export function useDraftFiles({
66
68
  if (incoming.length === 0) {
67
69
  return;
68
70
  }
71
+
69
72
  if (capabilities?.attachments === false) {
70
73
  setNotice(t('composer.notice.noAttachments', { agent: providerName ?? t('composer.notice.thisAgent') }));
74
+
71
75
  return;
72
76
  }
77
+
73
78
  const checked = checkAttachmentLimits(
74
79
  draft.attachments.length,
75
80
  incoming.map((file) => ({ name: file.name, mime: file.type, bytes: file.size, file })),
76
81
  draft.attachments.reduce((bytes, attachment) => bytes + uploadBytes(attachment), 0)
77
82
  );
83
+
78
84
  if (checked.rejected[0]) {
79
85
  setNotice(t('composer.notice.rejected', { name: checked.rejected[0].name || t('composer.notice.thatFile'), reason: checked.rejected[0].reason }));
80
86
  }
87
+
81
88
  if (checked.accepted.length === 0) {
82
89
  return;
83
90
  }
91
+
84
92
  readAttachments(checked.accepted.map((entry) => entry.file))
85
93
  .then((attachments) => setDraft((current) => ({ ...current, attachments: [...current.attachments, ...attachments] })))
86
94
  .catch(() => setNotice(t('composer.notice.unreadable')));
@@ -94,24 +102,32 @@ export function useDraftFiles({
94
102
  /* Files become attachments and a long text a file of its own; anything else the editor pastes as plain text. */
95
103
  const onPaste = (e: ClipboardEvent, view: EditorView): boolean => {
96
104
  const data = e.clipboardData;
105
+
97
106
  if (!data) {
98
107
  return false;
99
108
  }
109
+
100
110
  const pasted = filesOf(data);
111
+
101
112
  if (pasted.length > 0) {
102
113
  addFiles(pasted);
114
+
103
115
  return true;
104
116
  }
117
+
105
118
  const inline = pasteInline.current;
106
119
  pasteInline.current = false;
107
120
  const clip = data.getData('text/plain');
108
121
  const mentions = capabilities?.mentions === false ? [] : pastedMentions(clip);
122
+
109
123
  if (mentions.length > 0) {
110
124
  // The editor's own change reads the draft as it was rendered, so the mentions join it after that change.
111
125
  view.dispatch(view.state.replaceSelection(clip), { scrollIntoView: true, userEvent: 'input.paste' });
112
126
  setDraft((current) => ({ ...current, mentions: [...current.mentions, ...mentions.filter((path) => !current.mentions.includes(path))] }));
127
+
113
128
  return true;
114
129
  }
130
+
115
131
  // Without attachments there is nowhere else for the text to go, so it pastes as it always did.
116
132
  if (inline || capabilities?.attachments === false || !pasteBecomesAttachment(clip)) {
117
133
  const choices: PasteChoices = {
@@ -120,21 +136,29 @@ export function useDraftFiles({
120
136
  referenced: [...draft.chats, ...draft.references],
121
137
  mentions: capabilities?.mentions !== false
122
138
  };
139
+
123
140
  const chips = pasteWithChips(view, clip, readClipboardChips(data), choices);
141
+
124
142
  if (chips === null) {
125
143
  return false;
126
144
  }
145
+
127
146
  // After the editor's own change, which reads the draft as it was rendered.
128
147
  setDraft((current) => withPastedChips(current, chips, choices));
148
+
129
149
  return true;
130
150
  }
151
+
131
152
  // The paste still replaces what was selected; it just puts nothing in its place.
132
153
  const { from, to } = view.state.selection.main;
154
+
133
155
  if (from !== to) {
134
156
  view.dispatch({ changes: { from, to }, userEvent: 'delete' });
135
157
  }
158
+
136
159
  const name = pastedTextName(draft.attachments.map((attachment) => attachment.name));
137
160
  addFiles([new File([clip], name, { type: 'text/plain' })]);
161
+
138
162
  return true;
139
163
  };
140
164
 
@@ -27,12 +27,16 @@ export function useEffortStepper({ chatId, info, model, writable, controls, setN
27
27
  if (!writable || controls.configurationPending) {
28
28
  return;
29
29
  }
30
+
30
31
  const name = model?.name ?? info.selection.model;
31
32
  const result = stepEffort(model, info.selection, direction);
33
+
32
34
  if (result.kind === 'none') {
33
35
  setNotice(t('composer.notice.noEffort', { model: name }), 'info');
36
+
34
37
  return;
35
38
  }
39
+
36
40
  if (result.kind === 'edge') {
37
41
  setNotice(
38
42
  t(direction === 'up' ? 'composer.notice.effortHighest' : 'composer.notice.effortLowest', {
@@ -41,16 +45,20 @@ export function useEffortStepper({ chatId, info, model, writable, controls, setN
41
45
  }),
42
46
  'info'
43
47
  );
48
+
44
49
  return;
45
50
  }
51
+
46
52
  controls.configure({ selection: result.selection });
47
53
  setNotice(t('composer.notice.effort', { effort: choiceWord(EFFORT_OPTION, result.choice) }), 'info');
48
54
  };
49
55
 
50
56
  // A command means the chat as it stands when it is given, so the registration reads the latest step.
51
57
  const stepRef = useRef(step);
58
+
52
59
  useEffect(() => {
53
60
  stepRef.current = step;
54
61
  });
62
+
55
63
  useEffect(() => registerEffortStepper(scope.keyOf(chatId), (direction) => stepRef.current(direction)), [scope, chatId]);
56
64
  }
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useState } from 'react';
2
+ import { errorMessage } from '../../../transport';
2
3
 
3
4
  const NOTICE_MS = 4000;
4
5
 
@@ -20,7 +21,9 @@ export function useNotice(): ComposerNotice {
20
21
  if (!shown) {
21
22
  return;
22
23
  }
24
+
23
25
  const timer = setTimeout(() => setShown(null), NOTICE_MS);
26
+
24
27
  return () => clearTimeout(timer);
25
28
  }, [shown]);
26
29
 
@@ -30,6 +33,6 @@ export function useNotice(): ComposerNotice {
30
33
  notice: shown?.text ?? null,
31
34
  tone: shown?.tone ?? 'error',
32
35
  setNotice,
33
- showError: (error) => setNotice(error instanceof Error ? error.message : String(error))
36
+ showError: (error) => setNotice(errorMessage(error))
34
37
  };
35
38
  }
@@ -6,10 +6,13 @@ import { ChatPromptFeed } from '../../pending-prompts';
6
6
  export function usePendingRequests(chatId: string): PromptSubject[] {
7
7
  const scope = useChatScope();
8
8
  const [subjects, setSubjects] = useState<PromptSubject[]>([]);
9
+
9
10
  useEffect(() => {
10
11
  const feed = new ChatPromptFeed(scope, chatId, setSubjects);
11
12
  feed.start();
13
+
12
14
  return () => feed.dispose();
13
15
  }, [scope, chatId]);
16
+
14
17
  return subjects;
15
18
  }
@@ -1,4 +1,4 @@
1
- import { useMemo, useState } from 'react';
1
+ import { useState } from 'react';
2
2
  import { useChatRow } from '../../../state/chats';
3
3
 
4
4
  // How many sent prompts the arrow keys walk back through, per chat.
@@ -23,33 +23,40 @@ export function usePromptHistory(chatId: string, setText: (next: string, mention
23
23
  const items = useChatRow(chatId, (row) => row?.structure);
24
24
  const [index, setIndex] = useState<number | null>(null);
25
25
 
26
- const history = useMemo(() => {
27
- const prompts: Array<{ text: string; mentions: string[] }> = [];
28
- for (const id of order ?? []) {
29
- const item = items?.[id];
30
- if (item?.kind === 'user') {
31
- prompts.push({ text: item.text, mentions: item.mentions ?? [] });
32
- }
26
+ const prompts: Array<{ text: string; mentions: string[] }> = [];
27
+
28
+ for (const id of order ?? []) {
29
+ const item = items?.[id];
30
+
31
+ if (item?.kind === 'user') {
32
+ prompts.push({ text: item.text, mentions: item.mentions ?? [] });
33
33
  }
34
- // Walking back further than this is faster with the timeline than with the arrow key.
35
- return prompts.slice(-PROMPT_HISTORY);
36
- }, [order, items]);
34
+ }
35
+
36
+ // Walking back further than this is faster with the timeline than with the arrow key.
37
+ const history = prompts.slice(-PROMPT_HISTORY);
37
38
 
38
39
  const recall = (direction: -1 | 1): boolean => {
39
40
  if (history.length === 0) {
40
41
  return false;
41
42
  }
43
+
42
44
  const next = index === null ? (direction === -1 ? history.length - 1 : null) : index + direction;
45
+
43
46
  if (next === null || next >= history.length) {
44
47
  setIndex(null);
45
48
  setText('', [], []);
49
+
46
50
  return true;
47
51
  }
52
+
48
53
  if (next < 0) {
49
54
  return true;
50
55
  }
56
+
51
57
  setIndex(next);
52
58
  setText(history[next]!.text, history[next]!.mentions);
59
+
53
60
  return true;
54
61
  };
55
62
 
@@ -57,8 +64,10 @@ export function usePromptHistory(chatId: string, setText: (next: string, mention
57
64
  if (index === null) {
58
65
  return false;
59
66
  }
67
+
60
68
  setIndex(null);
61
69
  setText('', [], []);
70
+
62
71
  return true;
63
72
  };
64
73
 
@@ -31,6 +31,7 @@ export function usePromptHold(): ComposerPromptHold {
31
31
  const schedule = (): void => {
32
32
  clearTimer();
33
33
  const wait = holdLiftsIn({ typedAt: typedAt.current, composing: composing.current }, Date.now());
34
+
34
35
  if (wait !== null) {
35
36
  timer.current = setTimeout(() => release('pause'), wait);
36
37
  }
@@ -44,11 +45,13 @@ export function usePromptHold(): ComposerPromptHold {
44
45
 
45
46
  const setComposing = (next: boolean): void => {
46
47
  composing.current = next;
48
+
47
49
  if (next) {
48
50
  setState({ held: true, releasedBy: null });
49
51
  } else {
50
52
  typedAt.current = Date.now();
51
53
  }
54
+
52
55
  schedule();
53
56
  };
54
57
 
@@ -1,4 +1,4 @@
1
- import { useEffect, useId, useMemo, useState, type Dispatch, type RefObject, type SetStateAction } from 'react';
1
+ import { useEffect, useId, useState, type Dispatch, type RefObject, type SetStateAction } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { ensureSyntaxTree, syntaxTree } from '@codemirror/language';
4
4
  import type { EditorState } from '@codemirror/state';
@@ -108,11 +108,14 @@ export function usePromptMenus<D extends PromptDraft>({
108
108
  if (mention === null || cwd === null) {
109
109
  return;
110
110
  }
111
+
111
112
  let stale = false;
113
+
112
114
  const search = (): void => {
113
115
  if (searchFiles === null) {
114
116
  return;
115
117
  }
118
+
116
119
  searchFiles(scope.id, cwd, mention.query, MENTION_RESULTS)
117
120
  .then((files) => {
118
121
  if (!stale) {
@@ -122,14 +125,18 @@ export function usePromptMenus<D extends PromptDraft>({
122
125
  })
123
126
  .catch(() => undefined);
124
127
  };
128
+
125
129
  // A bare `@` is not a keystroke in a query. It opens the list of what is there, at once.
126
130
  if (mention.query === '') {
127
131
  search();
132
+
128
133
  return () => {
129
134
  stale = true;
130
135
  };
131
136
  }
137
+
132
138
  const timer = setTimeout(search, SEARCH_DEBOUNCE_MS);
139
+
133
140
  return () => {
134
141
  stale = true;
135
142
  clearTimeout(timer);
@@ -137,14 +144,13 @@ export function usePromptMenus<D extends PromptDraft>({
137
144
  }, [scope, cwd, mention, searchFiles]);
138
145
 
139
146
  const commandQuery = text.startsWith('/') && !text.includes('\n') ? text.slice(1).trim().toLowerCase() : null;
140
- const commands = useMemo((): ComposerCommand[] => {
141
- if (commandQuery === null) {
142
- return [];
143
- }
147
+
148
+ const commandsOf = (query: string): ComposerCommand[] => {
144
149
  const known = new Set(skills.map((skill) => skill.name));
145
150
  const own = localCommands.map((name) => ({ name, hint: t(`composer.commands.${name}`), local: true, skill: false }));
146
151
  // Every CLI with goals reads `/goal`, also one that lists no commands of its own.
147
152
  const goal = goals ? [{ name: 'goal', hint: t('composer.commands.goal'), local: false, skill: false }] : [];
153
+
148
154
  const cli = usableSlashCommands([...slashCommands])
149
155
  .filter((name) => !localCommands.includes(name) && (!goals || name !== 'goal'))
150
156
  .map((name) => ({
@@ -153,16 +159,12 @@ export function usePromptMenus<D extends PromptDraft>({
153
159
  local: false,
154
160
  skill: known.has(name)
155
161
  }));
156
- return [...own, ...goal, ...cli].filter((command) => command.name.startsWith(commandQuery)).slice(0, 8);
157
- }, [commandQuery, goals, localCommands, slashCommands, providerName, skills, t]);
158
162
 
159
- const skillMatches = useMemo(() => {
160
- if (skillQuery === null) {
161
- return [];
162
- }
163
- const query = skillQuery.query.toLowerCase();
164
- return skills.filter((skill) => skill.name.toLowerCase().includes(query)).slice(0, 8);
165
- }, [skillQuery, skills]);
163
+ return [...own, ...goal, ...cli].filter((command) => command.name.startsWith(query)).slice(0, 8);
164
+ };
165
+
166
+ const commands = commandQuery === null ? [] : commandsOf(commandQuery);
167
+ const skillMatches = skillQuery === null ? [] : skills.filter((skill) => skill.name.toLowerCase().includes(skillQuery.query.toLowerCase())).slice(0, 8);
166
168
 
167
169
  const commandMenuOpen = commandQuery !== null && commands.length > 0;
168
170
  const skillMenuOpen = !commandMenuOpen && skillQuery !== null && skillMatches.length > 0;
@@ -175,11 +177,13 @@ export function usePromptMenus<D extends PromptDraft>({
175
177
  const referenceMenuOpen = !commandMenuOpen && !skillMenuOpen && !mentionMenuOpen && referenceQuery !== null && offersReferences;
176
178
  const excluded = ownId === undefined ? [...draft.chats, ...draft.references] : [ownId, ...draft.chats, ...draft.references];
177
179
  const referenceMatches = referenceMenuOpen ? referenceSuggestions(referables, referenceQuery.query, excluded, REFERENCE_RESULTS) : [];
180
+
178
181
  // What the editor draws as chips and the send checks are still in the text, under the name each was picked with.
179
182
  const referenceChips = referenceTokens([...draft.chats, ...draft.references], referables.items, draft.referenceNames).map((token) => ({
180
183
  ...token,
181
184
  kind: referables.items.find((item) => item.id === token.id)?.kind ?? null
182
185
  }));
186
+
183
187
  // One menu is open at a time, so they share an id.
184
188
  const listId = useId();
185
189
  const menuOpen = commandMenuOpen || skillMenuOpen || mentionMenuOpen || referenceMenuOpen;
@@ -218,6 +222,7 @@ export function usePromptMenus<D extends PromptDraft>({
218
222
  if (!mention) {
219
223
  return;
220
224
  }
225
+
221
226
  const result = insertMention(text, mention, path);
222
227
  setText(result.text, draft.mentions.includes(path) ? draft.mentions : [...draft.mentions, path]);
223
228
  setMention(null);
@@ -226,6 +231,7 @@ export function usePromptMenus<D extends PromptDraft>({
226
231
 
227
232
  const chooseMentionAt = (index: number): void => {
228
233
  const path = mention === null ? null : mentionPick(index, searched, mention.query);
234
+
229
235
  if (path !== null) {
230
236
  chooseMention(path);
231
237
  }
@@ -239,9 +245,12 @@ export function usePromptMenus<D extends PromptDraft>({
239
245
  if (!referenceQuery) {
240
246
  return;
241
247
  }
248
+
242
249
  const result = insertReference(text, referenceQuery, item.name);
243
250
  const chat = item.kind === 'chat';
251
+
244
252
  const add = (ids: string[]): string[] => (ids.includes(item.id) ? ids : [...ids, item.id]);
253
+
245
254
  setDraft((current) => ({
246
255
  ...current,
247
256
  text: result.text,
@@ -249,12 +258,14 @@ export function usePromptMenus<D extends PromptDraft>({
249
258
  references: chat ? current.references : add(current.references),
250
259
  referenceNames: { ...current.referenceNames, [item.id]: item.name }
251
260
  }));
261
+
252
262
  setReferenceQuery(null);
253
263
  moveCaret(result.caret);
254
264
  };
255
265
 
256
266
  const chooseReferenceAt = (index: number): void => {
257
267
  const item = referenceMatches[index] ?? referenceMatches[0];
268
+
258
269
  if (item !== undefined) {
259
270
  chooseReference(item);
260
271
  }
@@ -274,10 +285,13 @@ export function usePromptMenus<D extends PromptDraft>({
274
285
  /* A clicked command; Tab completes one with a space after it instead. */
275
286
  const pickCommand = (command: ComposerCommand, index: number): void => {
276
287
  setMenuIndex(index);
288
+
277
289
  if (command.skill) {
278
290
  chooseSkill(command.name);
291
+
279
292
  return;
280
293
  }
294
+
281
295
  setText(`/${command.name}`);
282
296
  inputRef.current?.focus();
283
297
  };
@@ -286,8 +300,10 @@ export function usePromptMenus<D extends PromptDraft>({
286
300
  const completeCommand = (command: ComposerCommand): void => {
287
301
  if (command.skill) {
288
302
  chooseSkill(command.name);
303
+
289
304
  return;
290
305
  }
306
+
291
307
  setText(`/${command.name} `);
292
308
  };
293
309
 
@@ -295,12 +311,16 @@ export function usePromptMenus<D extends PromptDraft>({
295
311
  const moveInMenu = (key: string, count: number): boolean => {
296
312
  if (key === 'ArrowDown') {
297
313
  setMenuIndex((i) => (i + 1) % count);
314
+
298
315
  return true;
299
316
  }
317
+
300
318
  if (key === 'ArrowUp') {
301
319
  setMenuIndex((i) => (i - 1 + count) % count);
320
+
302
321
  return true;
303
322
  }
323
+
304
324
  return false;
305
325
  };
306
326
 
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
3
3
  import type { ChatAttachmentUpload, ChatQueuedMessage } from '@adecore/agent-contracts';
4
4
  import { useChatHost } from '../../../host';
5
5
  import { useChatScope } from '../../../scope';
6
- import { errorCode } from '../../../transport';
6
+ import { errorCode, errorMessage } from '../../../transport';
7
7
  import { useChatActions } from '../../actions';
8
8
  import { readStoredAttachments } from '../../attachments';
9
9
  import { takeBackIntoDraft, type ChatDraft } from '../../drafts';
@@ -33,26 +33,36 @@ export function useTakeBack({ chatId, draft: { latest, setDraft }, setNotice, on
33
33
  if (takingBack !== null) {
34
34
  return;
35
35
  }
36
+
36
37
  const failed = (e: unknown): void => {
37
- host.notify({ kind: 'error', title: t('composer.queue.editFailed'), description: e instanceof Error ? e.message : String(e) });
38
+ host.notify({ kind: 'error', title: t('composer.queue.editFailed'), description: errorMessage(e) });
38
39
  };
40
+
39
41
  const readFiles = async (): Promise<ChatAttachmentUpload[]> => {
40
42
  const stored = message.attachments ?? [];
43
+
41
44
  if (stored.length === 0) {
42
45
  return [];
43
46
  }
47
+
44
48
  const files = host.attachments;
49
+
45
50
  return readStoredAttachments((attachment) => files.read(scope.id, chatId, attachment.id), stored);
46
51
  };
52
+
47
53
  setTakingBack(message.id);
54
+
48
55
  try {
49
56
  let uploads: ChatAttachmentUpload[];
57
+
50
58
  try {
51
59
  uploads = await readFiles();
52
60
  } catch (e) {
53
61
  failed(e);
62
+
54
63
  return;
55
64
  }
65
+
56
66
  const taken: Omit<ChatDraft, 'quote' | 'quoteSource'> = {
57
67
  text: message.text,
58
68
  mentions: message.mentions ?? [],
@@ -61,12 +71,16 @@ export function useTakeBack({ chatId, draft: { latest, setDraft }, setNotice, on
61
71
  attachments: uploads,
62
72
  references: message.references ?? []
63
73
  };
74
+
64
75
  // A file that would not fit is refused here, while the message still holds it on the machine.
65
76
  const wouldReject = takeBackIntoDraft(latest.current, taken).rejected[0];
77
+
66
78
  if (wouldReject) {
67
79
  setNotice(t('composer.notice.queuedDoesNotFit', { name: wouldReject.name || t('composer.notice.thatFile'), reason: wouldReject.reason }));
80
+
68
81
  return;
69
82
  }
83
+
70
84
  try {
71
85
  await actions.unqueue(chatId, message.id);
72
86
  } catch (e) {
@@ -75,13 +89,17 @@ export function useTakeBack({ chatId, draft: { latest, setDraft }, setNotice, on
75
89
  } else {
76
90
  failed(e);
77
91
  }
92
+
78
93
  return;
79
94
  }
95
+
80
96
  const merged = takeBackIntoDraft(latest.current, taken);
81
97
  setDraft(merged.draft);
98
+
82
99
  if (merged.rejected[0]) {
83
100
  setNotice(t('composer.notice.rejected', { name: merged.rejected[0].name, reason: merged.rejected[0].reason }));
84
101
  }
102
+
85
103
  onTaken();
86
104
  } finally {
87
105
  setTakingBack(null);
@@ -1,4 +1,4 @@
1
- import { useMemo, useState } from 'react';
1
+ import { useState } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { Button } from '@adecore/ui';
4
4
  import { formatNumber } from '@adecore/ui/format';
@@ -9,23 +9,26 @@ export function ApprovalReview({ entries, maxHeight }: { entries: readonly Revie
9
9
  const { t } = useTranslation('agent-prompts');
10
10
  const [selected, setSelected] = useState<string | null>(null);
11
11
  const active = entries.find((entry) => entry.key === selected) ?? entries[0];
12
- const labels = useMemo(() => {
13
- const totals = new Map<string, number>();
14
- const counts = new Map<string, number>();
15
- for (const entry of entries) {
16
- totals.set(entry.path, (totals.get(entry.path) ?? 0) + 1);
17
- }
18
- return entries.map((entry) => {
19
- const count = (counts.get(entry.path) ?? 0) + 1;
20
- counts.set(entry.path, count);
21
- const total = totals.get(entry.path)!;
22
- const suffix = total > 1 ? ` (${formatNumber(count)}/${formatNumber(total)})` : '';
23
- return { short: `${entry.path.split(/[\\/]/).at(-1)}${suffix}`, full: `${entry.path}${suffix}` };
24
- });
25
- }, [entries]);
12
+ const totals = new Map<string, number>();
13
+ const counts = new Map<string, number>();
14
+
15
+ for (const entry of entries) {
16
+ totals.set(entry.path, (totals.get(entry.path) ?? 0) + 1);
17
+ }
18
+
19
+ const labels = entries.map((entry) => {
20
+ const count = (counts.get(entry.path) ?? 0) + 1;
21
+ counts.set(entry.path, count);
22
+ const total = totals.get(entry.path)!;
23
+ const suffix = total > 1 ? ` (${formatNumber(count)}/${formatNumber(total)})` : '';
24
+
25
+ return { short: `${entry.path.split(/[\\/]/).at(-1)}${suffix}`, full: `${entry.path}${suffix}` };
26
+ });
27
+
26
28
  if (active === undefined) {
27
29
  return null;
28
30
  }
31
+
29
32
  return (
30
33
  <div>
31
34
  {entries.length > 1 && (
@@ -23,6 +23,7 @@ export class DiffFallbackBoundary extends Component<Props, State> {
23
23
  if (state.resetKeys.length !== props.resetKeys.length || state.resetKeys.some((value, index) => !Object.is(value, props.resetKeys[index]))) {
24
24
  return { failed: false, resetKeys: props.resetKeys };
25
25
  }
26
+
26
27
  return null;
27
28
  }
28
29
 
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
4
4
  export function DiffOmitted({ path }: { path: string }) {
5
5
  useReviewSettled();
6
6
  const { t } = useTranslation('agent-chat');
7
+
7
8
  return (
8
9
  <div role="status" data-diff-omitted className="px-3 py-2 text-xs text-text-muted">
9
10
  <p className="truncate font-mono">{path}</p>
@@ -7,6 +7,7 @@ import { useReviewStack, type ReviewStack } from './use-review-stack';
7
7
 
8
8
  export function ReviewStackView({ entries, stack }: { entries: readonly ReviewEntry[]; stack: ReviewStack }) {
9
9
  const { t } = useTranslation('agent-chat');
10
+
10
11
  return (
11
12
  <div data-review-stack>
12
13
  {entries.map((entry) => (
@@ -41,5 +42,6 @@ export function ReviewStackView({ entries, stack }: { entries: readonly ReviewEn
41
42
 
42
43
  export function DiffStack({ entries }: { entries: readonly ReviewEntry[] }) {
43
44
  const stack = useReviewStack(entries);
45
+
44
46
  return <ReviewStackView entries={entries} stack={stack} />;
45
47
  }
@@ -4,6 +4,7 @@ import { useReviewSettled } from './review-settle';
4
4
  export function DiffUnavailable({ path }: { path: string }) {
5
5
  useReviewSettled();
6
6
  const { t } = useTranslation('agent-chat');
7
+
7
8
  return (
8
9
  <div role="status" data-diff-unavailable className="px-3 py-2 text-xs text-text-muted">
9
10
  <p className="truncate font-mono">{path}</p>