@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
@@ -32,11 +32,13 @@ interface OfferInput {
32
32
  /* The newest turn by when it started, so a thread that arrives out of order still names the turn that ran last. */
33
33
  function lastTurn(items: readonly ChatItem[]): Turn | null {
34
34
  let last: Turn | null = null;
35
+
35
36
  for (const item of items) {
36
37
  if (item.kind === 'turn' && (last === null || item.createdAt >= last.createdAt)) {
37
38
  last = item;
38
39
  }
39
40
  }
41
+
40
42
  return last;
41
43
  }
42
44
 
@@ -45,24 +47,27 @@ function offerableTurn({ info, compaction, items, dismissedTurnId }: OfferInput)
45
47
  if (compaction === undefined || compaction === 'none') {
46
48
  return null;
47
49
  }
50
+
48
51
  if (info.activeTurnId !== null || (info.queue ?? []).length > 0) {
49
52
  return null;
50
53
  }
54
+
51
55
  if (info.usage.contextTokens < RESUME_COMPACTION_TOKENS) {
52
56
  return null;
53
57
  }
58
+
54
59
  const turn = lastTurn(items);
60
+
55
61
  if (turn === null || turn.endedAt === null || turn.id === dismissedTurnId) {
56
62
  return null;
57
63
  }
58
- /*
59
- * `contextTokens` keeps the old number until the next request, so without this the offer would
60
- * come straight back after a compaction. A native compaction is a turn without a message of the
61
- * person, which is why this looks at the turn and not at the last thing that was said.
62
- */
64
+
65
+ // `contextTokens` keeps the old number until the next request, so the offer would return right after
66
+ // a compaction. A native one is a turn without a message of the person, hence the look at the turn.
63
67
  if (items.some((item) => item.kind === 'compaction' && item.turnId === turn.id)) {
64
68
  return null;
65
69
  }
70
+
66
71
  return turn;
67
72
  }
68
73
 
@@ -77,15 +82,18 @@ function dueAtOf(info: ChatInfo, endedAt: number): number {
77
82
  */
78
83
  export function resumeCompactionDueAt(input: OfferInput): number | null {
79
84
  const endedAt = offerableTurn(input)?.endedAt ?? null;
85
+
80
86
  return endedAt === null ? null : dueAtOf(input.info, endedAt);
81
87
  }
82
88
 
83
89
  /* Whether to offer compacting before the next message. */
84
90
  export function resumeCompactionOffer(input: OfferInput & { now: number }): ResumeCompactionOffer | null {
85
91
  const turn = offerableTurn(input);
92
+
86
93
  if (turn === null || turn.endedAt === null || input.now < dueAtOf(input.info, turn.endedAt)) {
87
94
  return null;
88
95
  }
96
+
89
97
  return {
90
98
  turnId: turn.id,
91
99
  tokens: input.info.usage.contextTokens,
@@ -109,30 +117,39 @@ export interface DueClock {
109
117
  export function watchDueAt(dueAt: number, due: (now: number) => void, clock: DueClock): () => void {
110
118
  let timer: unknown = null;
111
119
  let done = false;
120
+
112
121
  const check = (): void => {
113
122
  if (timer !== null) {
114
123
  clock.clearTimer(timer);
115
124
  timer = null;
116
125
  }
126
+
117
127
  if (done) {
118
128
  return;
119
129
  }
130
+
120
131
  const now = clock.now();
132
+
121
133
  if (now >= dueAt) {
122
134
  done = true;
123
135
  due(now);
136
+
124
137
  return;
125
138
  }
139
+
126
140
  timer = clock.setTimer(() => {
127
141
  timer = null;
128
142
  check();
129
143
  }, dueAt - now);
130
144
  };
145
+
131
146
  const stopWake = clock.onWake(check);
132
147
  check();
148
+
133
149
  return () => {
134
150
  done = true;
135
151
  stopWake();
152
+
136
153
  if (timer !== null) {
137
154
  clock.clearTimer(timer);
138
155
  }
@@ -2,11 +2,7 @@ import type { ChatBookmark } from '@adecore/agent-contracts';
2
2
  import type { TimelineRow } from './timeline';
3
3
  import { DEFAULT_READING_WIDTH, readingMeasure } from '../reading-width';
4
4
 
5
- /*
6
- * The strip at the left edge of a chat view with a tick per message a person sent, as numbers: which
7
- * rows get a tick, where the ticks go in the height the strip has, which messages are on screen, and
8
- * which one a pointer means. The component only draws what comes out of here.
9
- */
5
+ // The strip of ticks at the left edge of a chat view, as numbers; the component only draws what comes out of here.
10
6
 
11
7
  /* A message the person sent, a turn the machine opened with the results of tasks, or any other message with a bookmark on it. */
12
8
  export type TickKind = 'person' | 'wake' | 'bookmark';
@@ -33,6 +29,7 @@ const MIN_PITCH_PX = 4;
33
29
 
34
30
  /* The scroller's padding around the thread's content column (`px-4`). */
35
31
  const THREAD_PADDING_PX = 16;
32
+
36
33
  export const STRIP_WIDTH_PX = 24;
37
34
  /* The strip's distance from the view's left edge, and the air it keeps between itself and the thread's text. */
38
35
  export const STRIP_INSET_PX = 8;
@@ -48,23 +45,30 @@ const WIDTH_WAKE_PX = 5;
48
45
  export function ticksOf(rows: readonly TimelineRow[], marks: ReadonlyMap<string, ChatBookmark> = new Map()): ScrubberTick[] {
49
46
  return rows.flatMap((row, rowIndex): ScrubberTick[] => {
50
47
  const bookmark = marks.get(row.id) ?? null;
48
+
51
49
  if (row.kind === 'user') {
52
50
  // A message of only attachments still has a place in the thread; its names stand in for the text.
53
51
  const text = row.item.text !== '' ? row.item.text : (row.item.attachments ?? []).map((attachment) => attachment.name).join(', ');
52
+
54
53
  return [{ id: row.id, rowIndex, kind: 'person', text, createdAt: row.item.createdAt, turnId: row.item.turnId, bookmark }];
55
54
  }
55
+
56
56
  // The other turns nobody asked for (a sub-agent that finished, the CLI going on) are no place to return to.
57
57
  if (row.kind === 'turn-start' && ((row.turn.taskIds?.length ?? 0) > 0 || (row.turn.messageFrom?.length ?? 0) > 0)) {
58
58
  return [{ id: row.id, rowIndex, kind: 'wake', text: row.label, createdAt: row.turn.createdAt, turnId: row.turn.id, bookmark: null }];
59
59
  }
60
+
60
61
  if (bookmark === null) {
61
62
  return [];
62
63
  }
64
+
63
65
  if (row.kind === 'assistant') {
64
66
  return [{ id: row.id, rowIndex, kind: 'bookmark', text: row.item.text, createdAt: row.item.createdAt, turnId: row.item.turnId, bookmark }];
65
67
  }
68
+
66
69
  // The fold of a turn a marked reply is hidden in; the card reads the start the bookmark kept.
67
70
  const turnId = row.kind === 'turn-fold' ? row.turn.id : null;
71
+
68
72
  return [{ id: row.id, rowIndex, kind: 'bookmark', text: bookmark.excerpt, createdAt: bookmark.createdAt, turnId, bookmark }];
69
73
  });
70
74
  }
@@ -94,19 +98,23 @@ export function layoutTicks(kinds: readonly TickKind[], height: number, marked:
94
98
  const count = kinds.length;
95
99
  const room = Math.max(0, Math.floor(height));
96
100
  const slotCount = Math.min(count, Math.floor(room / MIN_PITCH_PX));
101
+
97
102
  if (slotCount === 0) {
98
103
  return { top: 0, pitch: MIN_PITCH_PX, count, slots: [] };
99
104
  }
105
+
100
106
  const pitch = Math.min(PITCH_PX, Math.floor(room / slotCount));
101
107
  const top = Math.floor((room - slotCount * pitch) / 2);
102
108
  const inset = Math.floor((pitch - TICK_HEIGHT_PX) / 2);
103
109
  const slots: TickSlot[] = [];
110
+
104
111
  for (let i = 0; i < slotCount; i++) {
105
112
  const first = Math.floor((i * count) / slotCount);
106
113
  const last = Math.floor(((i + 1) * count) / slotCount) - 1;
107
114
  // A merged tick is the person's as soon as one message in it is, since those are what the strip is for.
108
115
  const run = kinds.slice(first, last + 1);
109
116
  const kind = run.includes('person') ? 'person' : run.includes('bookmark') ? 'bookmark' : 'wake';
117
+
110
118
  slots.push({
111
119
  first,
112
120
  last,
@@ -116,6 +124,7 @@ export function layoutTicks(kinds: readonly TickKind[], height: number, marked:
116
124
  y: top + i * pitch + inset
117
125
  });
118
126
  }
127
+
119
128
  return { top, pitch, count, slots };
120
129
  }
121
130
 
@@ -127,6 +136,7 @@ export function tickOfRow(ticks: readonly ScrubberTick[], rowIndex: number): num
127
136
  if (ticks.length === 0) {
128
137
  return null;
129
138
  }
139
+
130
140
  return Math.max(
131
141
  0,
132
142
  lastIndexAtOrBelow(ticks.length, (index) => ticks[index]!.rowIndex, rowIndex)
@@ -136,25 +146,31 @@ export function tickOfRow(ticks: readonly ScrubberTick[], rowIndex: number): num
136
146
  /* Per tick, whether any of the rows has a find hit in its stretch. */
137
147
  export function ticksWithHits(ticks: readonly ScrubberTick[], hitRows: readonly number[]): boolean[] {
138
148
  const found = ticks.map(() => false);
149
+
139
150
  for (const row of hitRows) {
140
151
  const tick = tickOfRow(ticks, row);
152
+
141
153
  if (tick !== null) {
142
154
  found[tick] = true;
143
155
  }
144
156
  }
157
+
145
158
  return found;
146
159
  }
147
160
 
148
161
  /* The message under a height in the strip; inside a merged tick the height picks among its messages. */
149
162
  export function messageAt(layout: ScrubberLayout, y: number): number | null {
150
163
  const { slots, top, pitch } = layout;
164
+
151
165
  if (slots.length === 0) {
152
166
  return null;
153
167
  }
168
+
154
169
  const offset = y - top;
155
170
  const index = Math.min(slots.length - 1, Math.max(0, Math.floor(offset / pitch)));
156
171
  const slot = slots[index]!;
157
172
  const fraction = Math.min(1, Math.max(0, (offset - index * pitch) / pitch));
173
+
158
174
  return slot.first + Math.min(slot.last - slot.first, Math.floor(fraction * (slot.last - slot.first + 1)));
159
175
  }
160
176
 
@@ -162,9 +178,11 @@ export function slotOf(layout: ScrubberLayout, message: number): number | null {
162
178
  const { slots } = layout;
163
179
  let low = 0;
164
180
  let high = slots.length - 1;
181
+
165
182
  while (low <= high) {
166
183
  const middle = (low + high) >> 1;
167
184
  const slot = slots[middle]!;
185
+
168
186
  if (message < slot.first) {
169
187
  high = middle - 1;
170
188
  } else if (message > slot.last) {
@@ -173,6 +191,7 @@ export function slotOf(layout: ScrubberLayout, message: number): number | null {
173
191
  return middle;
174
192
  }
175
193
  }
194
+
176
195
  return null;
177
196
  }
178
197
 
@@ -184,9 +203,11 @@ export function tickWidth(kind: TickKind, distanceFromPointer: number | null): n
184
203
  if (distanceFromPointer === 0) {
185
204
  return WIDTH_HOVERED_PX;
186
205
  }
206
+
187
207
  if (distanceFromPointer === 1) {
188
208
  return kind === 'wake' ? WIDTH_NEAR_WAKE_PX : WIDTH_NEAR_PERSON_PX;
189
209
  }
210
+
190
211
  return kind === 'wake' ? WIDTH_WAKE_PX : WIDTH_PERSON_PX;
191
212
  }
192
213
 
@@ -195,8 +216,10 @@ function lastIndexAtOrBelow(length: number, valueAt: (index: number) => number,
195
216
  let low = 0;
196
217
  let high = length - 1;
197
218
  let found = -1;
219
+
198
220
  while (low <= high) {
199
221
  const middle = (low + high) >> 1;
222
+
200
223
  if (valueAt(middle) <= limit) {
201
224
  found = middle;
202
225
  low = middle + 1;
@@ -204,6 +227,7 @@ function lastIndexAtOrBelow(length: number, valueAt: (index: number) => number,
204
227
  high = middle - 1;
205
228
  }
206
229
  }
230
+
207
231
  return found;
208
232
  }
209
233
 
@@ -233,15 +257,19 @@ export interface MessageRange {
233
257
  */
234
258
  export function messagesInView({ starts, scrollTop, visibleHeight }: ReadingPosition): MessageRange | null {
235
259
  const bottom = scrollTop + visibleHeight;
260
+
236
261
  if (starts.length === 0 || visibleHeight <= 0 || starts.at(0)! >= bottom) {
237
262
  return null;
238
263
  }
264
+
239
265
  const first = Math.max(0, lastAtOrAbove(starts, scrollTop));
240
266
  let last = lastAtOrAbove(starts, bottom);
267
+
241
268
  // A row that starts on the very last line has not a pixel on screen yet.
242
269
  if (starts.at(last)! >= bottom) {
243
270
  last--;
244
271
  }
272
+
245
273
  return { first, last: Math.max(first, last) };
246
274
  }
247
275
 
@@ -262,13 +290,16 @@ export function stepMessage(starts: MessageStarts, eligible: (index: number) =>
262
290
  return i;
263
291
  }
264
292
  }
293
+
265
294
  return null;
266
295
  }
296
+
267
297
  for (let i = lastAtOrAbove(starts, scrollTop + 1) + 1; i < starts.length; i++) {
268
298
  if (eligible(i)) {
269
299
  return i;
270
300
  }
271
301
  }
302
+
272
303
  return null;
273
304
  }
274
305
 
@@ -282,7 +313,9 @@ export function threadPaddingLeft(width: number, strip: boolean, measure = readi
282
313
  if (!strip) {
283
314
  return THREAD_PADDING_PX;
284
315
  }
316
+
285
317
  // The column's margin is half of what the padding leaves, so the padding counts the clearance twice.
286
318
  const padding = 2 * STRIP_CLEARANCE_PX + measure + THREAD_PADDING_PX - Math.floor(width);
319
+
287
320
  return Math.min(STRIP_CLEARANCE_PX, Math.max(THREAD_PADDING_PX, padding));
288
321
  }
@@ -18,6 +18,7 @@ function headingOf(line: string): string | null {
18
18
  /* What a folded thought shows on its one line: the heading it opens with, or the whole thought flattened for the row to cut off. */
19
19
  export function thoughtPreview(text: string): string {
20
20
  const heading = headingOf(text.trimStart().split('\n', 1)[0] ?? '');
21
+
21
22
  return flattenThought(heading ?? text);
22
23
  }
23
24
 
@@ -30,11 +31,15 @@ export function thoughtLine(text: string): string {
30
31
  .split('\n')
31
32
  .map(headingOf)
32
33
  .filter((heading): heading is string => heading !== null);
34
+
33
35
  const last = headings.at(-1);
36
+
34
37
  if (last !== undefined) {
35
38
  return flattenThought(last);
36
39
  }
40
+
37
41
  const flat = flattenThought(text);
38
42
  const end = SENTENCE_END.exec(flat);
43
+
39
44
  return end === null ? flat : flat.slice(0, end.index + end[0].length);
40
45
  }
@@ -67,21 +67,27 @@ export function withTimedRows(rows: TimelineRow[], timed: readonly TimedRow[]):
67
67
  if (timed.length === 0) {
68
68
  return rows;
69
69
  }
70
+
70
71
  // Sorting is stable, which is what keeps rows of the same moment in the order they came.
71
72
  const pending = [...timed].sort((first, second) => first.at - second.at);
72
73
  const merged: TimelineRow[] = [];
73
74
  let next = 0;
75
+
74
76
  for (const row of rows) {
75
77
  const time = rowTime(row);
78
+
76
79
  while (time !== null && next < pending.length && pending[next]!.at < time) {
77
80
  merged.push(pending[next]!.row);
78
81
  next += 1;
79
82
  }
83
+
80
84
  merged.push(row);
81
85
  }
86
+
82
87
  for (; next < pending.length; next++) {
83
88
  merged.push(pending[next]!.row);
84
89
  }
90
+
85
91
  return merged;
86
92
  }
87
93
 
@@ -2,15 +2,8 @@ import type { TimelineRow } from './timeline';
2
2
  import { stripAnsi } from './ansi';
3
3
 
4
4
  /*
5
- * What a row of the thread puts on the clipboard. An answer is markdown on the wire and rendered
6
- * text on the screen, so copying it as text has to undo the marks the renderer swallowed; the
7
- * source itself stays available as "Copy as Markdown".
8
- */
9
-
10
- /*
11
- * The marks that only exist to be rendered come off, what they marked stays. `_` is left alone on
12
- * purpose: it is a letter in half the identifiers an agent writes, and no italic is worth mangling
13
- * `file_path`.
5
+ * An answer is markdown on the wire and rendered text on screen, so a copy as text undoes the marks the
6
+ * renderer swallowed. `_` stays: it is a letter in half the identifiers an agent writes (`file_path`).
14
7
  */
15
8
  export function stripMarkdown(text: string): string {
16
9
  return text
@@ -28,6 +21,7 @@ export function stripMarkdown(text: string): string {
28
21
  /* What a tool call has to show for itself: what it answered, or what it has answered so far. */
29
22
  function outputOf(tool: { output: string | null; progress?: { output: string | null } }): string | null {
30
23
  const output = tool.output ?? tool.progress?.output ?? null;
24
+
31
25
  return output === null ? null : stripAnsi(output);
32
26
  }
33
27
 
@@ -48,6 +42,7 @@ export function messageTextOf(row: TimelineRow): string | null {
48
42
  return outputOf(row.tool);
49
43
  case 'work-group': {
50
44
  const outputs = row.tools.map(outputOf).filter((output): output is string => output !== null);
45
+
51
46
  return outputs.length > 0 ? outputs.join('\n\n') : null;
52
47
  }
53
48
  case 'subagent':
@@ -28,14 +28,18 @@ export function withCurrentText(rows: TimelineRow[], items: Record<string, ChatI
28
28
  if (!items) {
29
29
  return rows;
30
30
  }
31
+
31
32
  return rows.map((row) => {
32
33
  const item = items[row.id];
34
+
33
35
  if (row.kind === 'assistant' && item?.kind === 'assistant') {
34
36
  return { ...row, item };
35
37
  }
38
+
36
39
  if (row.kind === 'thinking' && item?.kind === 'thinking') {
37
40
  return { ...row, item };
38
41
  }
42
+
39
43
  return row;
40
44
  });
41
45
  }
@@ -48,6 +52,7 @@ export function readTimelineTarget(element: HTMLElement, scroller: HTMLElement |
48
52
  const column = positiveInteger(file?.dataset.fileColumn);
49
53
  const endLine = positiveInteger(file?.dataset.fileEndLine);
50
54
  const path = file?.dataset.filePath ?? null;
55
+
51
56
  return {
52
57
  selection: selectionWithin(scroller),
53
58
  row: rows.find((row) => row.id === id) ?? null,
@@ -73,6 +78,7 @@ export function readTimelineTarget(element: HTMLElement, scroller: HTMLElement |
73
78
 
74
79
  function positiveInteger(value: string | undefined): number | null {
75
80
  const number = Number(value);
81
+
76
82
  return Number.isSafeInteger(number) && number > 0 ? number : null;
77
83
  }
78
84
 
@@ -87,31 +93,40 @@ export function uiReplyJump(
87
93
  hasEarlier: boolean
88
94
  ): UiReplyJump {
89
95
  const item = items?.[target.itemId];
96
+
90
97
  if (!item) {
91
98
  return { kind: hasEarlier ? 'earlier' : 'missing' };
92
99
  }
100
+
93
101
  if (item.kind !== 'assistant' || !item.ui?.some((block) => block.id === target.blockId && block.revision === target.revision)) {
94
102
  return { kind: 'missing' };
95
103
  }
104
+
96
105
  const index = rows.findIndex((row) => row.kind === 'assistant' && row.id === target.itemId);
106
+
97
107
  if (index !== -1) {
98
108
  return { kind: 'row', index };
99
109
  }
110
+
100
111
  return item.turnId && !expandedTurns.has(item.turnId) ? { kind: 'turn', turnId: item.turnId } : { kind: 'missing' };
101
112
  }
102
113
 
103
114
  export function revealUiReplyBlock(scroller: HTMLElement, target: UiReplyTarget): boolean {
104
115
  // Compare attributes instead of interpolating provider ids into CSS selectors.
105
116
  const row = Array.from(scroller.querySelectorAll<HTMLElement>('[data-item-id]')).find((element) => element.dataset.itemId === target.itemId);
117
+
106
118
  const block =
107
119
  row &&
108
120
  Array.from(row.querySelectorAll<HTMLElement>('[data-ui-block]')).find(
109
121
  (element) => element.dataset.uiBlock === target.blockId && element.dataset.uiRevision === target.revision
110
122
  );
123
+
111
124
  if (!block) {
112
125
  return false;
113
126
  }
127
+
114
128
  scroller.scrollTop += block.getBoundingClientRect().top - scroller.getBoundingClientRect().top;
115
129
  block.focus({ preventScroll: true });
130
+
116
131
  return true;
117
132
  }