@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
package/src/lazy.tsx CHANGED
@@ -13,6 +13,7 @@ const openErrorListeners = new Set<(error: unknown) => void>();
13
13
  */
14
14
  export function setLazyPrefetch(next: (load: Loader) => void): void {
15
15
  register = next;
16
+
16
17
  for (const load of loaders) {
17
18
  next(load);
18
19
  }
@@ -24,6 +25,7 @@ export function setLazyPrefetch(next: (load: Loader) => void): void {
24
25
  */
25
26
  export function onLazyOpenError(listener: (error: unknown) => void): () => void {
26
27
  openErrorListeners.add(listener);
28
+
27
29
  return () => {
28
30
  openErrorListeners.delete(listener);
29
31
  };
@@ -41,11 +43,14 @@ export function lazyNamed<Module extends Record<Name, ComponentType<any>>, Name
41
43
  ): ComponentType<ComponentProps<Module[Name]>> {
42
44
  // Through `LoadedComponent`, so a `LazyBoundary` around it draws the moment the module lands.
43
45
  const loaded = new LoadedComponent<ComponentType<any>>(async () => (await load())[name]);
46
+
44
47
  const open = (): Promise<ComponentType<any>> => loaded.load();
48
+
45
49
  if (options?.prefetch !== false) {
46
50
  loaders.push(open);
47
51
  register?.(open);
48
52
  }
53
+
49
54
  // Only a render loads through here; a prefetch calls `open` itself, so its failures report nothing.
50
55
  const Lazy = lazy(async () => {
51
56
  try {
@@ -54,14 +59,18 @@ export function lazyNamed<Module extends Record<Name, ComponentType<any>>, Name
54
59
  for (const listener of openErrorListeners) {
55
60
  listener(error);
56
61
  }
62
+
57
63
  throw error;
58
64
  }
59
65
  });
66
+
60
67
  const LazyNamed = (props: ComponentProps<Module[Name]>) => {
61
68
  // `loaded` fills in place; compiled, the element drawn before the module landed would stay.
62
69
  'use no memo';
63
70
  const Component: ComponentType<any> = loaded.current ?? Lazy;
71
+
64
72
  return <Component {...props} />;
65
73
  };
74
+
66
75
  return LazyNamed;
67
76
  }
@@ -27,12 +27,15 @@ export class MountedRegistry<T extends MountedEntry> extends Map<string, T> {
27
27
  */
28
28
  async reattachAll(reattach: (id: string, entry: T) => Promise<void>): Promise<void> {
29
29
  const queue = [...this];
30
+
30
31
  const work = async (): Promise<void> => {
31
32
  for (let next = queue.shift(); next !== undefined; next = queue.shift()) {
32
33
  const [id, entry] = next;
34
+
33
35
  if (entry.attached || this.get(id) !== entry) {
34
36
  continue;
35
37
  }
38
+
36
39
  try {
37
40
  await reattach(id, entry);
38
41
  } catch {
@@ -40,6 +43,7 @@ export class MountedRegistry<T extends MountedEntry> extends Map<string, T> {
40
43
  }
41
44
  }
42
45
  };
46
+
43
47
  await Promise.all(Array.from({ length: REATTACH_CONCURRENCY }, work));
44
48
  }
45
49
  }
@@ -34,32 +34,45 @@ export function portTransport(port: FramePort): PortTransport {
34
34
 
35
35
  const answer = (id: string, frame: { ok: true; result: unknown } | { ok: false; error: { code: string; message: string } }): void => {
36
36
  const entry = pending.get(id);
37
+
37
38
  if (!entry) {
38
39
  return;
39
40
  }
41
+
40
42
  pending.delete(id);
43
+
41
44
  if (!frame.ok) {
42
45
  entry.reject(new ChatTransportError(frame.error.code, frame.error.message));
46
+
43
47
  return;
44
48
  }
49
+
45
50
  const result = AGENT_REQUEST_SCHEMAS[entry.type].result.safeParse(frame.result);
51
+
46
52
  if (!result.success) {
47
53
  entry.reject(new ChatTransportError('bad-reply', i18next.t('agent-chat:transport.badReply', { type: entry.type })));
54
+
48
55
  return;
49
56
  }
57
+
50
58
  entry.resolve(result.data);
51
59
  };
52
60
 
53
61
  const dispatch = (event: string, payload: unknown): void => {
54
62
  if (!isEventType(event)) {
55
63
  console.warn('Dropped an unknown event', event);
64
+
56
65
  return;
57
66
  }
67
+
58
68
  const parsed = AGENT_EVENT_SCHEMAS[event].safeParse(payload);
69
+
59
70
  if (!parsed.success) {
60
71
  console.warn(`Dropped a malformed ${event} event`);
72
+
61
73
  return;
62
74
  }
75
+
63
76
  // A copy, so a handler that unsubscribes while it runs does not change the set being walked.
64
77
  for (const handler of [...(eventHandlers.get(event) ?? [])]) {
65
78
  (handler as (payload: unknown) => void)(parsed.data);
@@ -70,21 +83,30 @@ export function portTransport(port: FramePort): PortTransport {
70
83
  if (status === 'closed') {
71
84
  return;
72
85
  }
86
+
73
87
  const parsed = parseServerFrame(raw);
88
+
74
89
  if (!parsed.ok) {
75
90
  console.warn('Dropped a malformed frame', parsed.message);
91
+
76
92
  return;
77
93
  }
94
+
78
95
  const frame = parsed.value;
96
+
79
97
  if ('event' in frame) {
80
98
  dispatch(frame.event, frame.payload);
99
+
81
100
  return;
82
101
  }
102
+
83
103
  if (frame.id === null) {
84
104
  // An answer without an id is about a frame the host could not read; nothing waits for it.
85
105
  console.warn('The chat host refused a frame', frame);
106
+
86
107
  return;
87
108
  }
109
+
88
110
  answer(frame.id, frame);
89
111
  });
90
112
 
@@ -96,29 +118,36 @@ export function portTransport(port: FramePort): PortTransport {
96
118
  if (status !== 'open') {
97
119
  return Promise.reject(new ChatTransportError('not-connected', i18next.t('agent-chat:transport.notConnected')));
98
120
  }
121
+
99
122
  const id = String(nextId++);
123
+
100
124
  const promise = new Promise<ChatRequestMap[T]['result']>((resolve, reject) => {
101
125
  pending.set(id, { type, resolve: resolve as (result: unknown) => void, reject });
102
126
  });
127
+
103
128
  try {
104
129
  port.send({ id, type, payload });
105
130
  } catch {
106
131
  // A payload the port cannot clone throws here, and the caller only listens to the promise.
107
132
  pending.delete(id);
133
+
108
134
  return Promise.reject(new ChatTransportError('not-sent', i18next.t('agent-chat:transport.notSent', { type })));
109
135
  }
136
+
110
137
  return promise;
111
138
  },
112
139
  on<E extends AgentEventType>(event: E, handler: (payload: ChatEventMap[E]) => void): () => void {
113
140
  const handlers = eventHandlers.get(event) ?? new Set();
114
141
  eventHandlers.set(event, handlers);
115
142
  handlers.add(handler);
143
+
116
144
  return () => {
117
145
  handlers.delete(handler);
118
146
  };
119
147
  },
120
148
  subscribeStatus(handler: (status: ChatTransportStatus) => void): () => void {
121
149
  statusHandlers.add(handler);
150
+
122
151
  return () => {
123
152
  statusHandlers.delete(handler);
124
153
  };
@@ -127,13 +156,16 @@ export function portTransport(port: FramePort): PortTransport {
127
156
  if (status === 'closed') {
128
157
  return;
129
158
  }
159
+
130
160
  status = 'closed';
131
161
  stopListening();
132
162
  const waiting = [...pending.values()];
133
163
  pending.clear();
164
+
134
165
  for (const entry of waiting) {
135
166
  entry.reject(new ChatTransportError('disconnected', i18next.t('agent-chat:transport.closedBeforeAnswer')));
136
167
  }
168
+
137
169
  for (const handler of [...statusHandlers]) {
138
170
  handler(status);
139
171
  }
@@ -2,6 +2,7 @@
2
2
  export { focusPromptStart } from './logic/focus';
3
3
  export { PROMPT_SHORTCUTS, pageKey, type PromptKeyEvent } from './logic/keys';
4
4
  export { type PendingPrompt, type PromptAction, emptyPromptDraft, orderPrompts, type PromptAnswer, type PromptDraft } from './logic/prompts';
5
+
5
6
  export {
6
7
  type HostPrompt,
7
8
  type PromptSubject,
@@ -13,6 +14,7 @@ export {
13
14
  isBlockingSubject,
14
15
  answerPrompt
15
16
  } from './logic/subjects';
17
+
16
18
  export { useFocusAfterAnswer, type FocusAfterAnswer } from './logic/useFocusAfterAnswer';
17
19
  export { usePromptSession, type PromptSession } from './logic/usePromptSession';
18
20
  export { PromptPrimary, ApprovalActions, type ApprovalButton } from './ui/PromptActions';
@@ -5,15 +5,19 @@
5
5
  */
6
6
  export function focusPromptStart(root: ParentNode | null | undefined): boolean {
7
7
  const card = root?.querySelector<HTMLElement>('.prompt-card');
8
+
8
9
  if (!card) {
9
10
  return false;
10
11
  }
12
+
11
13
  const start =
12
14
  card.querySelector<HTMLElement>('[data-prompt-entry]:not(:disabled)') ??
13
15
  card.querySelector<HTMLElement>('[role="toolbar"] button:not(:disabled):not([data-prompt-primary]):not([data-prompt-lasting])') ??
14
16
  card.querySelector<HTMLElement>('[data-prompt-primary]:not(:disabled)') ??
15
17
  card.querySelector<HTMLElement>('.prompt-heading');
18
+
16
19
  start?.focus({ preventScroll: true });
20
+
17
21
  return start !== null;
18
22
  }
19
23
 
@@ -21,5 +25,6 @@ export function focusPromptStart(root: ParentNode | null | undefined): boolean {
21
25
  export function focusPromptHeading(root: ParentNode | null | undefined): boolean {
22
26
  const heading = root?.querySelector<HTMLElement>('.prompt-card .prompt-heading');
23
27
  heading?.focus({ preventScroll: true });
28
+
24
29
  return !!heading;
25
30
  }
@@ -30,6 +30,7 @@ export function stepIndex(index: number, count: number, to: ListStep): number {
30
30
  if (count === 0) {
31
31
  return -1;
32
32
  }
33
+
33
34
  switch (to) {
34
35
  case 'first':
35
36
  return 0;
@@ -50,19 +51,25 @@ export function choiceKey(event: PromptKeyEvent, { multiSelect, anyChosen }: { m
50
51
  if (!isPlain(event)) {
51
52
  return null;
52
53
  }
54
+
53
55
  const step = LIST_KEYS[event.key];
56
+
54
57
  if (step) {
55
58
  return { kind: 'move', to: step };
56
59
  }
60
+
57
61
  if (event.code === 'Space') {
58
62
  return { kind: 'pick' };
59
63
  }
64
+
60
65
  if (event.key === 'Enter') {
61
66
  if (!multiSelect) {
62
67
  return { kind: 'pick-and-commit' };
63
68
  }
69
+
64
70
  return anyChosen ? { kind: 'commit' } : { kind: 'pick' };
65
71
  }
72
+
66
73
  return null;
67
74
  }
68
75
 
@@ -74,13 +81,16 @@ export function answerFieldKey(
74
81
  if (!isPlain(event)) {
75
82
  return null;
76
83
  }
84
+
77
85
  if (event.key === 'ArrowUp' && belowChoices && caretAtStart) {
78
86
  return { kind: 'to-last-choice' };
79
87
  }
88
+
80
89
  if (event.key === 'Enter') {
81
90
  // An empty answer is not sent, and an empty line in front of the answer is never what Enter meant.
82
91
  return hasText ? { kind: 'commit' } : { kind: 'ignore' };
83
92
  }
93
+
84
94
  return null;
85
95
  }
86
96
 
@@ -101,9 +111,11 @@ export function pageKey(event: PromptKeyEvent, apple: boolean, typing: boolean):
101
111
  if (typing || event.isComposing) {
102
112
  return null;
103
113
  }
114
+
104
115
  if (matchesShortcut(PROMPT_SHORTCUTS.previousPrompt, event, apple)) {
105
116
  return -1;
106
117
  }
118
+
107
119
  return matchesShortcut(PROMPT_SHORTCUTS.nextPrompt, event, apple) ? 1 : null;
108
120
  }
109
121
 
@@ -2,17 +2,20 @@ import type { ChatApprovalItem, ChatQuestionItem, ChatQuestion } from '@adecore/
2
2
  import { toggleChoice } from '../../chat/logic/answers';
3
3
 
4
4
  export type PendingPrompt = ChatApprovalItem | ChatQuestionItem;
5
+
5
6
  export type PromptAction =
6
7
  | { kind: 'approve'; decision: 'allow' | 'allow-always' | 'deny'; message?: string }
7
8
  | { kind: 'answer'; answers: Record<string, string> }
8
9
  | { kind: 'dismiss' }
9
10
  // A terminal's permission request, answered with one of the choices the host offered.
10
11
  | { kind: 'choose'; choiceId: string };
12
+
11
13
  export interface PromptAnswer {
12
14
  choices: string[];
13
15
  text: string;
14
16
  custom: boolean;
15
17
  }
18
+
16
19
  export interface PromptDraft {
17
20
  index: number;
18
21
  answers: Record<string, PromptAnswer>;
@@ -23,9 +26,11 @@ export interface PromptDraft {
23
26
  export function emptyPromptDraft(): PromptDraft {
24
27
  return { index: 0, answers: {}, reason: '', showReason: false };
25
28
  }
29
+
26
30
  export function answerValue(answer: PromptAnswer): string {
27
31
  return answer.custom ? answer.text.trim() : answer.choices.join(', ');
28
32
  }
33
+
29
34
  export function questionAnswer(draft: PromptDraft, question: ChatQuestion): PromptAnswer {
30
35
  return draft.answers[question.id] ?? { choices: [], text: '', custom: question.choices.length === 0 };
31
36
  }
@@ -46,13 +51,16 @@ export function orderPrompts<T>(prompts: readonly T[], blocking: (prompt: T) =>
46
51
 
47
52
  export function nextPrompt(items: readonly PendingPrompt[], activeId: string | null): PendingPrompt | null {
48
53
  const active = items.find((item) => item.requestId === activeId);
54
+
49
55
  if (active) {
50
56
  return active;
51
57
  }
58
+
52
59
  return orderPrompts(items, isBlockingPrompt, (item) => item.createdAt)[0] ?? null;
53
60
  }
54
61
 
55
62
  export function promptAnswers(item: ChatQuestionItem, draft: PromptDraft): Record<string, string> | null {
56
63
  const answers = Object.fromEntries(item.questions.map((question) => [question.id, answerValue(questionAnswer(draft, question))]));
64
+
57
65
  return Object.values(answers).every((answer) => answer !== '') ? answers : null;
58
66
  }
@@ -78,8 +78,10 @@ export function approvalButtons(subject: PromptSubject, reason: string): Approva
78
78
  if (subject.kind !== 'chat' || subject.item.kind !== 'approval') {
79
79
  return [];
80
80
  }
81
+
81
82
  const { item } = subject;
82
83
  const message = reason.trim();
84
+
83
85
  return [
84
86
  ...(item.canAllowAlways && item.allowAlways
85
87
  ? [
@@ -116,18 +118,24 @@ export async function answerPrompt(
116
118
  if (subject.kind === 'host') {
117
119
  return host.prompts.answer(subject.prompt, action);
118
120
  }
121
+
119
122
  if (chat === null) {
120
123
  throw new Error(i18next.t('agent-prompts:error.notConnected'));
121
124
  }
125
+
122
126
  const { item, nodeId } = subject;
127
+
123
128
  if (action.kind === 'approve') {
124
129
  return chat.approve(nodeId, item.requestId, action.decision, action.message);
125
130
  }
131
+
126
132
  if (action.kind === 'answer') {
127
133
  return chat.answer(nodeId, item.requestId, action.answers);
128
134
  }
135
+
129
136
  if (action.kind === 'dismiss') {
130
137
  return chat.dismiss(nodeId, item.id);
131
138
  }
139
+
132
140
  throw new Error(i18next.t('agent-prompts:error.noChoices'));
133
141
  }
@@ -19,7 +19,9 @@ export function useFocusAfterAnswer(activeId: string | null, container: RefObjec
19
19
  if (!pending.current) {
20
20
  return;
21
21
  }
22
+
22
23
  pending.current = false;
24
+
23
25
  if (!focusPromptStart(container.current)) {
24
26
  onEmpty();
25
27
  }
@@ -1,4 +1,5 @@
1
1
  import { useRef, useState, type MouseEvent } from 'react';
2
+ import { errorMessage } from '../../transport';
2
3
  import { emptyPromptDraft, type PromptDraft } from './prompts';
3
4
 
4
5
  interface PromptSessionOptions<T> {
@@ -42,13 +43,16 @@ export function usePromptSession<T>({ prompts, idOf, pick, disabled }: PromptSes
42
43
  const busy = useRef(false);
43
44
  const pressedRequest = useRef<string | null>(null);
44
45
  const stillCompleted = completed.filter((id) => prompts.some((prompt) => idOf(prompt) === id));
46
+
45
47
  if (stillCompleted.length !== completed.length) {
46
48
  setCompleted(stillCompleted);
47
49
  }
50
+
48
51
  const waiting = prompts.filter((prompt) => !completed.includes(idOf(prompt)));
49
52
  const active = pick(waiting, activeId, activeIndex);
50
53
  const activeKey = active === null ? null : idOf(active);
51
54
  const index = active === null ? 0 : waiting.indexOf(active);
55
+
52
56
  if (index !== activeIndex) {
53
57
  setActiveIndex(index);
54
58
  }
@@ -60,32 +64,40 @@ export function usePromptSession<T>({ prompts, idOf, pick, disabled }: PromptSes
60
64
 
61
65
  const finish = (id: string) => {
62
66
  setCompleted((current) => [...current, id]);
67
+
63
68
  setDrafts((current) => {
64
69
  const next = { ...current };
65
70
  delete next[id];
71
+
66
72
  return next;
67
73
  });
68
74
  };
75
+
69
76
  const act = async (prompt: T, send: () => Promise<void>): Promise<'sent' | 'failed' | 'busy'> => {
70
77
  if (busy.current || disabled) {
71
78
  return 'busy';
72
79
  }
80
+
73
81
  const id = idOf(prompt);
74
82
  busy.current = true;
75
83
  setSending(true);
76
84
  setError(null);
85
+
77
86
  try {
78
87
  await send();
79
88
  finish(id);
89
+
80
90
  return 'sent';
81
91
  } catch (cause) {
82
- setError({ id, message: cause instanceof Error ? cause.message : String(cause) });
92
+ setError({ id, message: errorMessage(cause) });
93
+
83
94
  return 'failed';
84
95
  } finally {
85
96
  busy.current = false;
86
97
  setSending(false);
87
98
  }
88
99
  };
100
+
89
101
  return {
90
102
  active,
91
103
  activeId: activeKey,
@@ -32,6 +32,7 @@ function ApprovalDetails({ item }: { item: ChatApprovalItem }) {
32
32
  const input = typeof item.input === 'object' && item.input !== null ? (item.input as Record<string, unknown>) : {};
33
33
  // Some agents describe a command by the command itself, which the box under it already shows.
34
34
  const describes = item.description && item.description.trim() !== (typeof input.command === 'string' ? input.command.trim() : null);
35
+
35
36
  return (
36
37
  <>
37
38
  {describes && <p className="text-sm text-text-muted">{item.description}</p>}
@@ -82,6 +83,7 @@ export function ApprovalBody({
82
83
  const host = useChatHost();
83
84
  const { t } = useTranslation('agent-prompts');
84
85
  const AnswerField = host.dictation.Textarea;
86
+
85
87
  return (
86
88
  <>
87
89
  <ApprovalDetails item={item} />
@@ -32,6 +32,7 @@ export function ApprovalActions({
32
32
  sending: boolean;
33
33
  }) {
34
34
  const { t } = useTranslation('agent-prompts');
35
+
35
36
  return (
36
37
  <>
37
38
  {onAddReason && (
@@ -87,6 +88,7 @@ export function QuestionActions({
87
88
  onCommit(): void;
88
89
  }) {
89
90
  const { t } = useTranslation(['agent-prompts', 'agent-chat']);
91
+
90
92
  return (
91
93
  <>
92
94
  {index > 0 && (
@@ -35,6 +35,7 @@ interface PromptCardProps {
35
35
  function onCardKeyDown(event: KeyboardEvent<HTMLDivElement>, apple: boolean) {
36
36
  const card = event.currentTarget;
37
37
  const target = event.target instanceof HTMLElement ? event.target : null;
38
+
38
39
  if (!event.defaultPrevented) {
39
40
  if (isPrimaryKey(event.nativeEvent, apple)) {
40
41
  event.preventDefault();
@@ -46,6 +47,7 @@ function onCardKeyDown(event: KeyboardEvent<HTMLDivElement>, apple: boolean) {
46
47
  } else {
47
48
  const toolbar = target?.closest('[role="toolbar"]');
48
49
  const step = toolbar ? toolbarKey(event.nativeEvent) : null;
50
+
49
51
  if (toolbar && step !== null) {
50
52
  event.preventDefault();
51
53
  const buttons = [...toolbar.querySelectorAll<HTMLButtonElement>('button:not(:disabled)')];
@@ -54,6 +56,7 @@ function onCardKeyDown(event: KeyboardEvent<HTMLDivElement>, apple: boolean) {
54
56
  }
55
57
  }
56
58
  }
59
+
57
60
  if (event.defaultPrevented || staysInCard(event.nativeEvent)) {
58
61
  event.stopPropagation();
59
62
  }
@@ -63,6 +66,7 @@ function onCardKeyDown(event: KeyboardEvent<HTMLDivElement>, apple: boolean) {
63
66
  export function PromptCard({ kind, heading, meta, top, actions, busy, notice, error, children }: PromptCardProps) {
64
67
  const { t } = useTranslation('agent-prompts');
65
68
  const { isApplePlatform } = useChatHost();
69
+
66
70
  const card = (
67
71
  <div
68
72
  className="prompt-card flex min-h-0 flex-col gap-2 p-3"
@@ -96,9 +100,11 @@ export function PromptCard({ kind, heading, meta, top, actions, busy, notice, er
96
100
  </div>
97
101
  </div>
98
102
  );
103
+
99
104
  if (top === undefined) {
100
105
  return card;
101
106
  }
107
+
102
108
  return (
103
109
  <div className="flex min-h-0 flex-col">
104
110
  {top}
@@ -26,23 +26,29 @@ export function PromptView(props: PromptViewProps) {
26
26
  const question = item.kind === 'question' ? item.questions[Math.min(draft.index, item.questions.length - 1)]! : null;
27
27
  const answer = question ? questionAnswer(draft, question) : null;
28
28
  const last = item.kind === 'question' && draft.index === item.questions.length - 1;
29
+
29
30
  // Whether Next or Answer may go with this draft. The keys ask it as well as the button.
30
31
  const readyWith = (next: PromptDraft): boolean =>
31
32
  item.kind === 'question' && !!question && !!answerValue(questionAnswer(next, question)) && !(last && !promptAnswers(item, next));
33
+
32
34
  const commit = (next: PromptDraft) => {
33
35
  if (item.kind !== 'question' || locked || !readyWith(next)) {
34
36
  return;
35
37
  }
38
+
36
39
  if (!last) {
37
40
  onDraft({ ...next, index: next.index + 1 });
38
41
  } else {
39
42
  const answers = promptAnswers(item, next);
43
+
40
44
  if (answers) {
41
45
  onAction({ kind: 'answer', answers });
42
46
  }
43
47
  }
44
48
  };
49
+
45
50
  const multiple = item.kind === 'question' && item.questions.length > 1;
51
+
46
52
  const meta =
47
53
  subject.source !== undefined || more > 0 || hasDraft || multiple ? (
48
54
  <>
@@ -38,7 +38,9 @@ export function QuestionBody({
38
38
  if (shownQuestion.current === question.id) {
39
39
  return;
40
40
  }
41
+
41
42
  shownQuestion.current = question.id;
43
+
42
44
  if (document.activeElement === null || document.activeElement === document.body) {
43
45
  (items.current[tabStop] ?? items.current[0])?.focus({ preventScroll: true });
44
46
  }
@@ -49,10 +51,13 @@ export function QuestionBody({
49
51
  multiSelect: question.multiSelect,
50
52
  anyChosen: !answer.custom && answer.choices.length > 0
51
53
  });
54
+
52
55
  if (action === null) {
53
56
  return;
54
57
  }
58
+
55
59
  event.preventDefault();
60
+
56
61
  if (action.kind === 'move') {
57
62
  items.current[stepIndex(index, count, action.to)]?.focus();
58
63
  } else if (action.kind === 'commit') {
@@ -60,6 +65,7 @@ export function QuestionBody({
60
65
  } else {
61
66
  const next = pickPromptChoice(answer, question, label);
62
67
  onAnswer(next);
68
+
63
69
  if (action.kind === 'pick-and-commit') {
64
70
  onCommit(next);
65
71
  }
@@ -68,15 +74,19 @@ export function QuestionBody({
68
74
 
69
75
  const onFieldKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
70
76
  const field = event.currentTarget;
77
+
71
78
  const action = answerFieldKey(event.nativeEvent, {
72
79
  belowChoices: question.choices.length > 0,
73
80
  caretAtStart: field.selectionStart === 0 && field.selectionEnd === 0,
74
81
  hasText: field.value.trim() !== ''
75
82
  });
83
+
76
84
  if (action === null) {
77
85
  return;
78
86
  }
87
+
79
88
  event.preventDefault();
89
+
80
90
  if (action.kind === 'to-last-choice') {
81
91
  items.current[question.choices.length - 1]?.focus();
82
92
  } else if (action.kind === 'commit') {
@@ -91,6 +101,7 @@ export function QuestionBody({
91
101
  <div role={question.multiSelect ? 'group' : 'radiogroup'} aria-label={question.question} className="flex flex-col gap-2">
92
102
  {question.choices.map((choice, index) => {
93
103
  const selected = !answer.custom && answer.choices.includes(choice.label);
104
+
94
105
  return (
95
106
  <button
96
107
  key={choice.label}
@@ -4,5 +4,6 @@ import { useChatHost } from '../host';
4
4
  /* The colors an account may wear, the host's palette. */
5
5
  export function AccountColors({ value, label, onChange }: { value: string | undefined; label: string; onChange(id: string): void }) {
6
6
  const { all, featured, label: labelOf } = useChatHost().accents;
7
+
7
8
  return <AccentSwatches value={value} label={label} accents={all} featured={featured} labelOf={labelOf} onValueChange={onChange} />;
8
9
  }
@@ -32,14 +32,19 @@ interface AccountDetailProps {
32
32
  /* The name a person types, saved when they leave the field; an empty one goes back to what was there. */
33
33
  function NameField({ value, label, onSave }: { value: string; label: string; onSave(name: string): void }) {
34
34
  const [draft, setDraft] = useState(value);
35
+
35
36
  const commit = (): void => {
36
37
  const name = draft.trim();
38
+
37
39
  if (name === '' || name === value) {
38
40
  setDraft(value);
41
+
39
42
  return;
40
43
  }
44
+
41
45
  onSave(name);
42
46
  };
47
+
43
48
  return (
44
49
  <Input
45
50
  className="w-55 max-w-full"
@@ -80,9 +85,11 @@ export function AccountDetail({ provider, entry, login, canOpenLogin, loginBlock
80
85
  if (trouble !== null) {
81
86
  return trouble;
82
87
  }
88
+
83
89
  if (!signedIn) {
84
90
  return t('account.login.signedOutDetail');
85
91
  }
92
+
86
93
  return status?.plan ? t('account.login.planDetail', { plan: status.plan }) : t('account.login.detail');
87
94
  };
88
95