@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
@@ -63,6 +63,7 @@ const THEMATIC_BREAK = /^ {0,3}([-*_])(?:[ \t]*\1){2,}[ \t]*$/;
63
63
  // A heading or a quote, which ends a list item instead of continuing its paragraph.
64
64
  const ENDS_ITEM = /^ {0,3}(?:#{1,6}(?:[ \t]|$)|>)/;
65
65
  const HTML_START = /^ {0,3}<[A-Za-z/!?]/;
66
+
66
67
  // The HTML that runs on until a marker rather than a blank line, with that marker.
67
68
  const HTML_UNTIL: [RegExp, RegExp][] = [
68
69
  [/^ {0,3}<(?:pre|script|style|textarea)(?:[ \t>]|$)/i, /<\/(?:pre|script|style|textarea)>/i],
@@ -71,6 +72,7 @@ const HTML_UNTIL: [RegExp, RegExp][] = [
71
72
  [/^ {0,3}<![A-Za-z]/, />/],
72
73
  [/^ {0,3}<!\[CDATA\[/, /\]\]>/]
73
74
  ];
75
+
74
76
  // A reference or footnote definition may be used anywhere in the text, so a block cannot stand alone.
75
77
  const DEFINITION = /^ {0,3}\[[^\]]+\]:/m;
76
78
  // Markdown ends a line at a lone carriage return too, which a split at line feeds would not see.
@@ -94,6 +96,7 @@ export function splitMarkdownBlocks(text: string): MarkdownBlock[] {
94
96
  if (DEFINITION.test(text) || LONE_RETURN.test(text)) {
95
97
  return [{ text, openFence: isFenceOpenAtEnd(text) }];
96
98
  }
99
+
97
100
  const lines = text.split('\n');
98
101
  const blocks: MarkdownBlock[] = [];
99
102
  let start = 0;
@@ -121,6 +124,7 @@ export function splitMarkdownBlocks(text: string): MarkdownBlock[] {
121
124
  if (end > start) {
122
125
  blocks.push({ text: text.slice(start, end), openFence: false });
123
126
  }
127
+
124
128
  start = end;
125
129
  };
126
130
 
@@ -134,6 +138,7 @@ export function splitMarkdownBlocks(text: string): MarkdownBlock[] {
134
138
  const blankCut = lineFence === null && afterBlank && !blank && !CONTINUES.test(line);
135
139
  lineFence = nextFence(lineFence, line);
136
140
  afterBlank = lineFence === null && blank && index > 0;
141
+
137
142
  if (top !== null) {
138
143
  if (closesFence(top.marker, content)) {
139
144
  closed = { fence: top, source: text.slice(top.from, at + content.length) };
@@ -143,36 +148,46 @@ export function splitMarkdownBlocks(text: string): MarkdownBlock[] {
143
148
  top.body += (top.ending ?? '') + outdent(content, top.indent);
144
149
  top.ending = line.length > content.length ? '\r\n' : '\n';
145
150
  }
151
+
146
152
  continue;
147
153
  }
154
+
148
155
  if (closed !== null) {
149
156
  if (empty) {
150
157
  continue;
151
158
  }
159
+
152
160
  blocks.push(fenceBlock(text.slice(start, at), closed.fence, closed.source, true));
153
161
  start = at;
154
162
  closed = null;
155
163
  }
164
+
156
165
  if (html !== null) {
157
166
  // Raw HTML holds no fence and no blank line to cut at until its marker.
158
167
  if (html.test(content)) {
159
168
  html = null;
160
169
  }
170
+
161
171
  continue;
162
172
  }
173
+
163
174
  if (known && fence !== null && item !== null && !empty && columns(content) < item.column) {
164
175
  // Code is no paragraph a line may continue lazily, so a line left of the item's content ends
165
176
  // the item and its code. A next item of the same list keeps the list going.
166
177
  fence = null;
178
+
167
179
  if (!LIST_MARKER.test(content)) {
168
180
  item = null;
169
181
  }
170
182
  }
183
+
171
184
  if (blankCut && fence === null) {
172
185
  cutAt(at);
173
186
  }
187
+
174
188
  const above = index > 0 ? withoutReturn(lines[index - 1]!) : '';
175
189
  const opening = known && fence === null ? openingFence(content) : null;
190
+
176
191
  if (opening !== null && EMPTY_ITEM.test(above)) {
177
192
  // Markdown reads an empty item right above a fence as a list or as text depending on what
178
193
  // stands before it, so the two stay in one block.
@@ -185,12 +200,15 @@ export function splitMarkdownBlocks(text: string): MarkdownBlock[] {
185
200
  top = { ...opening, from: at + opening.indent, body: '', ending: null };
186
201
  item = null;
187
202
  blankAbove = null;
203
+
188
204
  continue;
189
205
  }
206
+
190
207
  if (known && item?.empty === true && empty) {
191
208
  // An item with nothing on its first line ends at a blank line right under it.
192
209
  item = null;
193
210
  }
211
+
194
212
  if (known && fence === null && !empty) {
195
213
  // A fence in an item is followed only while the item is known to be open, and less than four
196
214
  // columns deeper than its content: deeper it is code of the item, or of an item inside it.
@@ -200,19 +218,24 @@ export function splitMarkdownBlocks(text: string): MarkdownBlock[] {
200
218
  item = nextItem(item, content, index > 0 && !BLANK.test(above));
201
219
  }
202
220
  }
221
+
203
222
  if (fence === null && HTML_START.test(content)) {
204
223
  known = false;
205
224
  const until = HTML_UNTIL.find(([begins]) => begins.test(content))?.[1];
206
225
  html = until !== undefined && !until.test(content) ? until : null;
207
226
  }
227
+
208
228
  if (known && (fence !== null || item !== null)) {
209
229
  fence = nextFence(fence, fence === null ? itemContent(content) : content);
210
230
  }
231
+
211
232
  if (item?.empty === true && !empty && columns(content) >= item.column) {
212
233
  item = { ...item, empty: false };
213
234
  }
235
+
214
236
  blankAbove = empty ? at : null;
215
237
  }
238
+
216
239
  if (top !== null) {
217
240
  blocks.push(fenceBlock(text.slice(start), top, text.slice(top.from), false));
218
241
  } else if (closed !== null) {
@@ -220,6 +243,7 @@ export function splitMarkdownBlocks(text: string): MarkdownBlock[] {
220
243
  } else {
221
244
  blocks.push({ text: text.slice(start), openFence: (known ? fence : lineFence) !== null });
222
245
  }
246
+
223
247
  return blocks;
224
248
  }
225
249
 
@@ -241,21 +265,28 @@ export function settledBlocksText(text: string): string {
241
265
  function withoutTrailingTitles(text: string): string {
242
266
  const lines = text.split('\n');
243
267
  let end = lines.length;
268
+
244
269
  for (let index = lines.length - 1; index >= 0; index--) {
245
270
  const line = lines[index]!;
271
+
246
272
  if (BLANK.test(line)) {
247
273
  continue;
248
274
  }
275
+
249
276
  // A bold line right under a line of text continues that paragraph.
250
277
  const title = HEADING.test(line) || (BOLD_LINE.test(line) && (index === 0 || BLANK.test(lines[index - 1]!)));
278
+
251
279
  if (!title) {
252
280
  break;
253
281
  }
282
+
254
283
  end = index;
255
284
  }
285
+
256
286
  if (end === lines.length) {
257
287
  return text;
258
288
  }
289
+
259
290
  return lines
260
291
  .slice(0, end)
261
292
  .map((line) => `${line}\n`)
@@ -265,46 +296,58 @@ function withoutTrailingTitles(text: string): string {
265
296
  /* The fence a line leaves open: it opens one, closes the one that is open, or changes nothing. */
266
297
  function nextFence(fence: OpenFence | null, line: string): OpenFence | null {
267
298
  const match = FENCE.exec(line);
299
+
268
300
  if (!match) {
269
301
  return fence;
270
302
  }
303
+
271
304
  const indent = match[1]!.length;
272
305
  const run = match[2]!;
273
306
  const rest = match[3]!;
307
+
274
308
  if (fence === null) {
275
309
  // A backtick fence's info string may not hold a backtick, or the line is inline code.
276
310
  if (run[0] === '`' && rest.includes('`')) {
277
311
  return null;
278
312
  }
313
+
279
314
  return { marker: run[0]!, length: run.length, indent };
280
315
  }
316
+
281
317
  // A closing fence carries no info string, and a line far deeper than the opening is code.
282
318
  if (run[0] === fence.marker && run.length >= fence.length && BLANK.test(rest) && indent <= fence.indent + CLOSING_INDENT) {
283
319
  return null;
284
320
  }
321
+
285
322
  return fence;
286
323
  }
287
324
 
288
325
  function isFenceOpenAtEnd(text: string): boolean {
289
326
  let fence: OpenFence | null = null;
327
+
290
328
  for (const line of text.split('\n')) {
291
329
  fence = nextFence(fence, withoutReturn(line));
292
330
  }
331
+
293
332
  return fence !== null;
294
333
  }
295
334
 
296
335
  /* The fence a line opens wherever it stands, with its indentation in columns. */
297
336
  function openingFence(content: string): Pick<TopFence, 'marker' | 'info' | 'indent' | 'direct'> | null {
298
337
  const match = FENCE.exec(content);
338
+
299
339
  if (!match) {
300
340
  return null;
301
341
  }
342
+
302
343
  const marker = match[2]!;
303
344
  const info = match[3]!.replace(/^[ \t]+|[ \t]+$/g, '');
345
+
304
346
  // A backtick fence's info string may not hold a backtick, or the line is inline code.
305
347
  if (marker[0] === '`' && info.includes('`')) {
306
348
  return null;
307
349
  }
350
+
308
351
  return { marker, info, indent: columns(content), direct: !/[\\&]/.test(info.split(/[ \t]/, 1)[0]!) };
309
352
  }
310
353
 
@@ -315,6 +358,7 @@ function withoutReturn(line: string): string {
315
358
 
316
359
  function closesFence(marker: string, content: string): boolean {
317
360
  const run = CLOSING_FENCE.exec(content)?.[1];
361
+
318
362
  return run !== undefined && run[0] === marker[0] && run.length >= marker.length;
319
363
  }
320
364
 
@@ -322,38 +366,48 @@ function fenceBlock(text: string, fence: TopFence, source: string, closed: boole
322
366
  if (!fence.direct) {
323
367
  return { text, openFence: !closed };
324
368
  }
369
+
325
370
  return { text, openFence: !closed, fence: { marker: fence.marker, info: fence.info, body: fence.body, closed, source } };
326
371
  }
327
372
 
328
373
  /* The list item a line that is neither blank nor a fence leaves open, as far as a fence needs to know. */
329
374
  function nextItem(item: ListItem | null, content: string, underLine: boolean): ListItem | null {
330
375
  const indent = columns(content);
376
+
331
377
  if (item !== null && indent >= item.column) {
332
378
  return item;
333
379
  }
380
+
334
381
  if (THEMATIC_BREAK.test(content) || ENDS_ITEM.test(content)) {
335
382
  return null;
336
383
  }
384
+
337
385
  const marker = indent < CODE_INDENT ? itemMarker(content) : null;
386
+
338
387
  if (marker === null) {
339
388
  if (item === null || !underLine) {
340
389
  return null;
341
390
  }
391
+
342
392
  // A line right under an item may continue its paragraph, or follow an item that ends in something else.
343
393
  return { ...item, certain: false };
344
394
  }
395
+
345
396
  // Right under another line, only a bullet or a 1 with text after it surely starts a list; anything
346
397
  // else may be text of a paragraph above.
347
398
  const certain = !underLine || (item !== null && item.certain && item.kind === marker.kind) || (marker.first && marker.rest !== '');
399
+
348
400
  return { column: marker.column, kind: marker.kind, certain, empty: marker.rest === '' };
349
401
  }
350
402
 
351
403
  /* The marker a list item starts with: its kind, whether it may start a list under text, and the column its content starts at. */
352
404
  function itemMarker(content: string): { kind: string; first: boolean; column: number; rest: string } | null {
353
405
  const match = LIST_MARKER.exec(content);
406
+
354
407
  if (match === null) {
355
408
  return null;
356
409
  }
410
+
357
411
  const [, bullet, number, delimiter, spacing = '', rest = ''] = match;
358
412
  const indent = columns(content);
359
413
  const width = bullet !== undefined ? 1 : number!.length + 1;
@@ -361,18 +415,21 @@ function itemMarker(content: string): { kind: string; first: boolean; column: nu
361
415
  // item starts with indented code one column after the marker.
362
416
  const gap = rest === '' ? 1 : columns(spacing, indent + width);
363
417
  const column = indent + width + (gap > CODE_INDENT ? 1 : gap);
418
+
364
419
  return { kind: bullet ?? delimiter!, first: bullet !== undefined || Number(number) === 1, column, rest };
365
420
  }
366
421
 
367
422
  /* A list item's line with its marker as spaces, so a fence on that line is seen where the item's content starts. */
368
423
  function itemContent(content: string): string {
369
424
  const marker = itemMarker(content);
425
+
370
426
  return marker === null ? content : ' '.repeat(marker.column) + marker.rest;
371
427
  }
372
428
 
373
429
  /* The columns of the indentation of a line, which starts at column `from`; a tab reaches to the next stop of four. */
374
430
  function columns(line: string, from = 0): number {
375
431
  let column = from;
432
+
376
433
  for (const character of line) {
377
434
  if (character === '\t') {
378
435
  column += TAB_STOP - (column % TAB_STOP);
@@ -382,6 +439,7 @@ function columns(line: string, from = 0): number {
382
439
  break;
383
440
  }
384
441
  }
442
+
385
443
  return column - from;
386
444
  }
387
445
 
@@ -392,20 +450,26 @@ function columns(line: string, from = 0): number {
392
450
  function outdent(line: string, indent: number): string {
393
451
  let column = 0;
394
452
  let index = 0;
453
+
395
454
  while (column < indent && index < line.length) {
396
455
  const character = line[index];
456
+
397
457
  if (character === '\t') {
398
458
  const stop = column + TAB_STOP - (column % TAB_STOP);
459
+
399
460
  if (stop > indent) {
400
461
  return ' '.repeat(stop - indent) + line.slice(index + 1);
401
462
  }
463
+
402
464
  column = stop;
403
465
  } else if (character === ' ') {
404
466
  column += 1;
405
467
  } else {
406
468
  break;
407
469
  }
470
+
408
471
  index += 1;
409
472
  }
473
+
410
474
  return line.slice(index);
411
475
  }
@@ -57,15 +57,19 @@ function attributeText(value: string): string {
57
57
  */
58
58
  export function themedSvg(svg: string, colors: Readonly<Record<string, string>>): string {
59
59
  const root = ROOT.exec(svg);
60
+
60
61
  if (root === null) {
61
62
  return svg;
62
63
  }
64
+
63
65
  const variables = attributeText(
64
66
  Object.entries(colors)
65
67
  .map(([name, value]) => `${name}:${value};`)
66
68
  .join('')
67
69
  );
70
+
68
71
  const tag = STYLE.test(root[0]) ? root[0].replace(STYLE, (style) => style + variables) : root[0].replace(/^<svg\b/, `<svg style="${variables}"`);
72
+
69
73
  return svg.slice(0, root.index) + tag + svg.slice(root.index + root[0].length);
70
74
  }
71
75
 
@@ -21,18 +21,22 @@ function answerAt(node: Node): Element | null {
21
21
  */
22
22
  function selectsOutside(range: Range, answer: Element): boolean {
23
23
  const walker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_TEXT);
24
+
24
25
  for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
25
26
  if (!range.intersectsNode(node) || answer.contains(node)) {
26
27
  continue;
27
28
  }
29
+
28
30
  const text = node.textContent ?? '';
29
31
  const start = node === range.startContainer ? range.startOffset : 0;
30
32
  const end = node === range.endContainer ? range.endOffset : text.length;
31
33
  const parent = node.parentElement;
34
+
32
35
  if (text.slice(start, end).trim() !== '' && parent !== null && getComputedStyle(parent).userSelect !== 'none') {
33
36
  return true;
34
37
  }
35
38
  }
39
+
36
40
  return false;
37
41
  }
38
42
 
@@ -41,15 +45,21 @@ export function selectedAnswerQuote(selection: Selection | null, thread: HTMLEle
41
45
  if (thread === null || selection === null || selection.isCollapsed || selection.rangeCount === 0) {
42
46
  return null;
43
47
  }
48
+
44
49
  const range = selection.getRangeAt(0);
45
50
  const answer = answerAt(range.startContainer) ?? answerAt(range.endContainer);
51
+
46
52
  if (answer === null || !thread.contains(answer)) {
47
53
  return null;
48
54
  }
55
+
49
56
  const inside = answer.contains(range.startContainer) && answer.contains(range.endContainer);
57
+
50
58
  if (!inside && selectsOutside(range, answer)) {
51
59
  return null;
52
60
  }
61
+
53
62
  const text = quoteOf(selection.toString());
63
+
54
64
  return text === null ? null : { text, itemId: answer.closest('[data-find-item]')?.getAttribute('data-find-item') ?? null };
55
65
  }
@@ -35,6 +35,7 @@ function valueOf(segment: Exclude<ChipSegment, { kind: 'text' }>): string {
35
35
  if (segment.kind === 'mention') {
36
36
  return segment.path;
37
37
  }
38
+
38
39
  return segment.kind === 'skill' ? segment.name : segment.id;
39
40
  }
40
41
 
@@ -59,9 +60,11 @@ function split(parent: HastParent, tokenize: (text: string) => ChipSegment[], sk
59
60
  if (child.type === 'text') {
60
61
  return tokenize(child.value).map((segment) => (segment.kind === 'text' ? { type: 'text', value: segment.text } : chipElement(segment)));
61
62
  }
63
+
62
64
  if (child.type === 'element' && !skipped.has(child.tagName)) {
63
65
  split(child, tokenize, skipped);
64
66
  }
67
+
65
68
  return [child];
66
69
  });
67
70
  }
@@ -72,6 +75,7 @@ export function rehypeChips({ mentions = [], skills = [], references = [] }: Chi
72
75
  if (mentions.length === 0 && skills.length === 0 && references.length === 0) {
73
76
  return;
74
77
  }
78
+
75
79
  split(tree, (text) => tokenizeChips(text, mentions, skills, references), SKIPPED);
76
80
  };
77
81
  }
@@ -82,6 +86,7 @@ export function rehypeNamedReferences({ items, referenced = [] }: NamedReference
82
86
  if (items.length === 0) {
83
87
  return;
84
88
  }
89
+
85
90
  split(tree, (text) => (text.includes('#') ? tokenizeNamedReferences(text, items, referenced) : [{ kind: 'text', text }]), SKIPPED_IN_REPLIES);
86
91
  };
87
92
  }
@@ -45,9 +45,11 @@ function wrap(parent: HastParent): void {
45
45
  : { type: 'element', tagName: 'span', properties: { className: [FADE_CLASS] }, children: [{ type: 'text', value: segment }] }
46
46
  );
47
47
  }
48
+
48
49
  if (child.type === 'element' && !SKIPPED.has(child.tagName)) {
49
50
  wrap(child);
50
51
  }
52
+
51
53
  return [child];
52
54
  });
53
55
  }
@@ -13,13 +13,18 @@ function literal(parent: MdastNode): void {
13
13
  if (parent.children === undefined) {
14
14
  return;
15
15
  }
16
+
16
17
  const flow = FLOW_PARENTS.has(parent.type);
18
+
17
19
  parent.children = parent.children.map((child): MdastNode => {
18
20
  if (child.type !== 'html') {
19
21
  literal(child);
22
+
20
23
  return child;
21
24
  }
25
+
22
26
  const text = { ...child, type: 'text' };
27
+
23
28
  return flow ? { type: 'paragraph', position: child.position, children: [text] } : text;
24
29
  });
25
30
  }
@@ -34,9 +34,11 @@ export function advanceReveal(position: number, target: number, elapsedMs: numbe
34
34
  if (position >= target) {
35
35
  return target;
36
36
  }
37
+
37
38
  const elapsed = Math.min(Math.max(elapsedMs, 0), REVEAL_MAX_FRAME_MS);
38
39
  const eased = (target - position) * (1 - Math.exp(-elapsed / REVEAL_TAU_MS));
39
40
  const floor = ((finished ? REVEAL_FINISH_MIN_CPS : REVEAL_MIN_CPS) * elapsed) / 1000;
41
+
40
42
  return Math.min(target, position + Math.max(eased, floor));
41
43
  }
42
44
 
@@ -48,18 +50,24 @@ export function advanceReveal(position: number, target: number, elapsedMs: numbe
48
50
  */
49
51
  export function revealBoundary(text: string, position: number, complete: boolean): number {
50
52
  const end = Math.min(text.length, Math.max(0, Math.floor(position)));
53
+
51
54
  if (end === text.length && complete) {
52
55
  return end;
53
56
  }
57
+
54
58
  let cut = end;
59
+
55
60
  while (cut > 0 && !isSpace(text, cut) && !isSpace(text, cut - 1)) {
56
61
  cut -= 1;
57
62
  }
63
+
58
64
  if (end - cut <= REVEAL_MAX_HELD_WORD) {
59
65
  return cut;
60
66
  }
67
+
61
68
  // Half of a surrogate pair renders as a replacement box.
62
69
  const last = text.charCodeAt(end - 1);
70
+
63
71
  return last >= 0xd800 && last <= 0xdbff ? end - 1 : end;
64
72
  }
65
73
 
@@ -91,6 +99,7 @@ export function useRevealedText(text: string, streaming: boolean): RevealedText
91
99
  if (!streaming || activeRef.current) {
92
100
  return;
93
101
  }
102
+
94
103
  position.current = latest.current.text.length;
95
104
  shownRef.current = revealBoundary(latest.current.text, position.current, false);
96
105
  activeRef.current = true;
@@ -102,9 +111,11 @@ export function useRevealedText(text: string, streaming: boolean): RevealedText
102
111
  if (!active) {
103
112
  return;
104
113
  }
114
+
105
115
  let frame = 0;
106
116
  let previous: number | null = null;
107
117
  let grewAt = -Infinity;
118
+
108
119
  const tick = (now: number) => {
109
120
  const elapsed = previous === null ? 0 : now - previous;
110
121
  previous = now;
@@ -112,24 +123,31 @@ export function useRevealedText(text: string, streaming: boolean): RevealedText
112
123
  const finished = !current.streaming;
113
124
  position.current = advanceReveal(position.current, current.text.length, elapsed, finished);
114
125
  const length = revealBoundary(current.text, position.current, finished);
126
+
115
127
  if (length !== shownRef.current) {
116
128
  shownRef.current = length;
117
129
  grewAt = now;
118
130
  setShown(length);
119
131
  }
132
+
120
133
  if (finished && length >= current.text.length && now - grewAt >= FADE_SETTLE_MS) {
121
134
  activeRef.current = false;
122
135
  setActive(false);
136
+
123
137
  return;
124
138
  }
139
+
125
140
  frame = requestAnimationFrame(tick);
126
141
  };
142
+
127
143
  frame = requestAnimationFrame(tick);
144
+
128
145
  return () => cancelAnimationFrame(frame);
129
146
  }, [active]);
130
147
 
131
148
  if (!active) {
132
149
  return { text, active: false };
133
150
  }
151
+
134
152
  return { text: text.slice(0, Math.min(shown, text.length)), active: true };
135
153
  }
@@ -16,12 +16,15 @@ export function ForksRow({ chatId, turnId }: { chatId: string; turnId: string })
16
16
  const { t } = useTranslation('agent-chat');
17
17
  const forks = useForkIdsAfter(chatId, turnId);
18
18
  const label = t('work.forksAfter', { count: forks.length });
19
+
19
20
  if (forks.length === 0) {
20
21
  return null;
21
22
  }
23
+
22
24
  if (forks.length === 1) {
23
25
  return <OneFork forkId={forks[0]!} label={label} />;
24
26
  }
27
+
25
28
  return (
26
29
  <div className="flex">
27
30
  <Menu.Root>
@@ -53,6 +56,7 @@ function ForksLabel({ label, link = true }: { label: string; link?: boolean }) {
53
56
  // A fork no longer in this project has nowhere to lead, so it only says it is there.
54
57
  function OneFork({ forkId, label }: { forkId: string; label: string }) {
55
58
  const place = useHostChatPlace(forkId);
59
+
56
60
  return (
57
61
  <div className="flex">
58
62
  {place.title === null ? (
@@ -71,6 +75,7 @@ function OneFork({ forkId, label }: { forkId: string; label: string }) {
71
75
  function ForkItem({ forkId }: { forkId: string }) {
72
76
  const { t } = useTranslation('agent-chat');
73
77
  const place = useHostChatPlace(forkId);
78
+
74
79
  return (
75
80
  <Menu.Item disabled={place.title === null} onClick={place.go}>
76
81
  <Icon icon={GitFork} size={14} /> <span className="truncate">{place.title ?? t('fork.pill.label')}</span>
@@ -27,13 +27,17 @@ const savedPaths = new Map<string, string>();
27
27
  /* True for `ms` after `start()`, for a check mark that says an action landed. */
28
28
  function useFlash(ms: number): [boolean, () => void] {
29
29
  const [on, setOn] = useState(false);
30
+
30
31
  useEffect(() => {
31
32
  if (!on) {
32
33
  return;
33
34
  }
35
+
34
36
  const timer = setTimeout(() => setOn(false), ms);
37
+
35
38
  return () => clearTimeout(timer);
36
39
  }, [on, ms]);
40
+
37
41
  return [on, () => setOn(true)];
38
42
  }
39
43
 
@@ -71,15 +75,18 @@ function SavedFile({ path }: { path: string }) {
71
75
  const { id } = useChatScope();
72
76
  const { fileLinks } = useChatHost();
73
77
  const openFileLink = useOpenFileLink();
78
+
74
79
  const chip = (
75
80
  <>
76
81
  <FileIcon path={path} size={14} />
77
82
  <span className="truncate">{path}</span>
78
83
  </>
79
84
  );
85
+
80
86
  if (fileLinks === null) {
81
87
  return <span className={clsx(CHIP_IN_MESSAGE, MENTION_TONE)}>{chip}</span>;
82
88
  }
89
+
83
90
  return (
84
91
  <button
85
92
  type="button"
@@ -124,8 +131,10 @@ function ReadyImage({ chatId, attachment, prompt, transparent }: { chatId: strin
124
131
  ? undefined
125
132
  : async (): Promise<void> => {
126
133
  setSaving(true);
134
+
127
135
  try {
128
136
  const path = await saveToProject(scopeId, chatId, attachment.id, attachment.name);
137
+
129
138
  if (path !== null) {
130
139
  savedPaths.set(savedKey, path);
131
140
  setSavedPath(path);
@@ -229,6 +238,7 @@ export function GeneratedImageRow({ chatId, tool: derived }: { chatId: string; t
229
238
  const { t } = useTranslation('agent-chat');
230
239
  const tool = useCurrentItem(chatId, derived);
231
240
  const view = generatedImageView(tool);
241
+
232
242
  switch (view.state) {
233
243
  case 'generating':
234
244
  return <HeadLine label={t('generatedImage.generating')} live startedAt={toolStartedAt(tool)} />;