@agent-native/toolkit 0.228.0-nightly-20261111212500 → 0.228.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 (361) hide show
  1. package/agent-native.eject.json +2 -0
  2. package/dist/app/agentkit/react/approval-card.d.ts.map +1 -1
  3. package/dist/app/agentkit/react/approval-card.js +5 -3
  4. package/dist/app/agentkit/react/approval-card.js.map +1 -1
  5. package/dist/app/agentkit/react/code-block.d.ts +32 -0
  6. package/dist/app/agentkit/react/code-block.d.ts.map +1 -0
  7. package/dist/app/agentkit/react/code-block.js +125 -0
  8. package/dist/app/agentkit/react/code-block.js.map +1 -0
  9. package/dist/app/agentkit/react/components.d.ts +33 -14
  10. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  11. package/dist/app/agentkit/react/components.js +843 -168
  12. package/dist/app/agentkit/react/components.js.map +1 -1
  13. package/dist/app/agentkit/react/context-usage.d.ts +45 -0
  14. package/dist/app/agentkit/react/context-usage.d.ts.map +1 -0
  15. package/dist/app/agentkit/react/context-usage.js +93 -0
  16. package/dist/app/agentkit/react/context-usage.js.map +1 -0
  17. package/dist/app/agentkit/react/context.d.ts +42 -3
  18. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  19. package/dist/app/agentkit/react/context.js +46 -1
  20. package/dist/app/agentkit/react/context.js.map +1 -1
  21. package/dist/app/agentkit/react/disclosure-content.d.ts +12 -0
  22. package/dist/app/agentkit/react/disclosure-content.d.ts.map +1 -0
  23. package/dist/app/agentkit/react/disclosure-content.js +33 -0
  24. package/dist/app/agentkit/react/disclosure-content.js.map +1 -0
  25. package/dist/app/agentkit/react/duration.d.ts +6 -0
  26. package/dist/app/agentkit/react/duration.d.ts.map +1 -0
  27. package/dist/app/agentkit/react/duration.js +21 -0
  28. package/dist/app/agentkit/react/duration.js.map +1 -0
  29. package/dist/app/agentkit/react/file-change.d.ts +33 -0
  30. package/dist/app/agentkit/react/file-change.d.ts.map +1 -0
  31. package/dist/app/agentkit/react/file-change.js +83 -0
  32. package/dist/app/agentkit/react/file-change.js.map +1 -0
  33. package/dist/app/agentkit/react/file-diff.d.ts +17 -0
  34. package/dist/app/agentkit/react/file-diff.d.ts.map +1 -0
  35. package/dist/app/agentkit/react/file-diff.js +94 -0
  36. package/dist/app/agentkit/react/file-diff.js.map +1 -0
  37. package/dist/app/agentkit/react/index.d.ts +7 -0
  38. package/dist/app/agentkit/react/index.d.ts.map +1 -1
  39. package/dist/app/agentkit/react/index.js +7 -0
  40. package/dist/app/agentkit/react/index.js.map +1 -1
  41. package/dist/app/agentkit/react/question-card.d.ts +67 -0
  42. package/dist/app/agentkit/react/question-card.d.ts.map +1 -0
  43. package/dist/app/agentkit/react/question-card.js +198 -0
  44. package/dist/app/agentkit/react/question-card.js.map +1 -0
  45. package/dist/app/agentkit/react/root.d.ts +3 -2
  46. package/dist/app/agentkit/react/root.d.ts.map +1 -1
  47. package/dist/app/agentkit/react/root.js +2 -2
  48. package/dist/app/agentkit/react/root.js.map +1 -1
  49. package/dist/app/agentkit/react/roving-focus.d.ts +4 -0
  50. package/dist/app/agentkit/react/roving-focus.d.ts.map +1 -0
  51. package/dist/app/agentkit/react/roving-focus.js +75 -0
  52. package/dist/app/agentkit/react/roving-focus.js.map +1 -0
  53. package/dist/app/agentkit/react/stack-trace.d.ts +34 -0
  54. package/dist/app/agentkit/react/stack-trace.d.ts.map +1 -0
  55. package/dist/app/agentkit/react/stack-trace.js +121 -0
  56. package/dist/app/agentkit/react/stack-trace.js.map +1 -0
  57. package/dist/app/agentkit/react/stop-button.d.ts.map +1 -1
  58. package/dist/app/agentkit/react/stop-button.js +22 -6
  59. package/dist/app/agentkit/react/stop-button.js.map +1 -1
  60. package/dist/app/agentkit/react/styles.css +1306 -395
  61. package/dist/app/agentkit/react/terminal.d.ts +28 -0
  62. package/dist/app/agentkit/react/terminal.d.ts.map +1 -0
  63. package/dist/app/agentkit/react/terminal.js +84 -0
  64. package/dist/app/agentkit/react/terminal.js.map +1 -0
  65. package/dist/app/agentkit/react/trace.d.ts +31 -0
  66. package/dist/app/agentkit/react/trace.d.ts.map +1 -0
  67. package/dist/app/agentkit/react/trace.js +73 -0
  68. package/dist/app/agentkit/react/trace.js.map +1 -0
  69. package/dist/app/blocks/library/HighlightedCode.d.ts +2 -1
  70. package/dist/app/blocks/library/HighlightedCode.d.ts.map +1 -1
  71. package/dist/app/blocks/library/HighlightedCode.js +8 -111
  72. package/dist/app/blocks/library/HighlightedCode.js.map +1 -1
  73. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  74. package/dist/app/chat/AgentKitAssistantChat.js +23 -245
  75. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  76. package/dist/app/chat/agentkit-chat/guided-questions.d.ts +15 -1
  77. package/dist/app/chat/agentkit-chat/guided-questions.d.ts.map +1 -1
  78. package/dist/app/chat/agentkit-chat/guided-questions.js +142 -2
  79. package/dist/app/chat/agentkit-chat/guided-questions.js.map +1 -1
  80. package/dist/app/chat/agentkit-chat/history.js +1 -1
  81. package/dist/app/chat/agentkit-chat/history.js.map +1 -1
  82. package/dist/app/chat/agentkit-chat/index.d.ts +1 -1
  83. package/dist/app/chat/agentkit-chat/index.d.ts.map +1 -1
  84. package/dist/app/chat/agentkit-chat/index.js +1 -1
  85. package/dist/app/chat/agentkit-chat/index.js.map +1 -1
  86. package/dist/app/chat/agentkit-chat/labels.d.ts +5 -0
  87. package/dist/app/chat/agentkit-chat/labels.d.ts.map +1 -0
  88. package/dist/app/chat/agentkit-chat/labels.js +245 -0
  89. package/dist/app/chat/agentkit-chat/labels.js.map +1 -0
  90. package/dist/app/chat/agentkit-chat/questions.d.ts +1 -1
  91. package/dist/app/chat/agentkit-chat/questions.d.ts.map +1 -1
  92. package/dist/app/chat/agentkit-chat/questions.js +1 -1
  93. package/dist/app/chat/agentkit-chat/questions.js.map +1 -1
  94. package/dist/app/chat/agentkit-chat/root.d.ts.map +1 -1
  95. package/dist/app/chat/agentkit-chat/root.js +5 -64
  96. package/dist/app/chat/agentkit-chat/root.js.map +1 -1
  97. package/dist/app/chat/agentkit-chat/work-step-labels.d.ts +2 -0
  98. package/dist/app/chat/agentkit-chat/work-step-labels.d.ts.map +1 -1
  99. package/dist/app/chat/agentkit-chat/work-step-labels.js +31 -0
  100. package/dist/app/chat/agentkit-chat/work-step-labels.js.map +1 -1
  101. package/dist/app/chat/chat/markdown-renderer.d.ts +2 -12
  102. package/dist/app/chat/chat/markdown-renderer.d.ts.map +1 -1
  103. package/dist/app/chat/chat/markdown-renderer.js +2 -41
  104. package/dist/app/chat/chat/markdown-renderer.js.map +1 -1
  105. package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
  106. package/dist/app/chat/chat/tool-call-display.js +2 -2
  107. package/dist/app/chat/chat/tool-call-display.js.map +1 -1
  108. package/dist/app/chat/conversation/AgentConversation.d.ts.map +1 -1
  109. package/dist/app/chat/conversation/AgentConversation.js +2 -37
  110. package/dist/app/chat/conversation/AgentConversation.js.map +1 -1
  111. package/dist/app/chat/tool-cells/BashCell.d.ts +3 -1
  112. package/dist/app/chat/tool-cells/BashCell.d.ts.map +1 -1
  113. package/dist/app/chat/tool-cells/BashCell.js +4 -40
  114. package/dist/app/chat/tool-cells/BashCell.js.map +1 -1
  115. package/dist/app/chat/tool-cells/EditCell.d.ts.map +1 -1
  116. package/dist/app/chat/tool-cells/EditCell.js +5 -108
  117. package/dist/app/chat/tool-cells/EditCell.js.map +1 -1
  118. package/dist/app/chat/tool-cells/FilesChangedSummary.d.ts.map +1 -1
  119. package/dist/app/chat/tool-cells/FilesChangedSummary.js +23 -81
  120. package/dist/app/chat/tool-cells/FilesChangedSummary.js.map +1 -1
  121. package/dist/app/chat/tool-cells/WriteCell.d.ts.map +1 -1
  122. package/dist/app/chat/tool-cells/WriteCell.js +5 -24
  123. package/dist/app/chat/tool-cells/WriteCell.js.map +1 -1
  124. package/dist/app/code-highlighter.d.ts +11 -0
  125. package/dist/app/code-highlighter.d.ts.map +1 -0
  126. package/dist/app/code-highlighter.js +122 -0
  127. package/dist/app/code-highlighter.js.map +1 -0
  128. package/dist/app/context-xray/ThreadContextRing.js +3 -3
  129. package/dist/app/context-xray/ThreadContextRing.js.map +1 -1
  130. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  131. package/dist/app/i18n/catalogs/ar-SA.js +22 -0
  132. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  133. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  134. package/dist/app/i18n/catalogs/de-DE.js +22 -0
  135. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  136. package/dist/app/i18n/catalogs/en-US.d.ts +22 -0
  137. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  138. package/dist/app/i18n/catalogs/en-US.js +22 -0
  139. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  140. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  141. package/dist/app/i18n/catalogs/es-ES.js +22 -0
  142. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  143. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  144. package/dist/app/i18n/catalogs/fr-FR.js +22 -0
  145. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  146. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  147. package/dist/app/i18n/catalogs/hi-IN.js +22 -0
  148. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  149. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  150. package/dist/app/i18n/catalogs/ja-JP.js +22 -0
  151. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  152. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  153. package/dist/app/i18n/catalogs/ko-KR.js +22 -0
  154. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  155. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  156. package/dist/app/i18n/catalogs/pt-BR.js +22 -0
  157. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  158. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  159. package/dist/app/i18n/catalogs/zh-CN.js +22 -0
  160. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  161. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  162. package/dist/app/i18n/catalogs/zh-TW.js +22 -0
  163. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  164. package/dist/app/onboarding/first-run-first-paint.d.ts +10 -0
  165. package/dist/app/onboarding/first-run-first-paint.d.ts.map +1 -0
  166. package/dist/app/onboarding/first-run-first-paint.js +22 -0
  167. package/dist/app/onboarding/first-run-first-paint.js.map +1 -0
  168. package/dist/app/onboarding/first-run-startup-gate.d.ts +3 -1
  169. package/dist/app/onboarding/first-run-startup-gate.d.ts.map +1 -1
  170. package/dist/app/onboarding/first-run-startup-gate.js +30 -6
  171. package/dist/app/onboarding/first-run-startup-gate.js.map +1 -1
  172. package/dist/app/providers/AppProviders.d.ts.map +1 -1
  173. package/dist/app/providers/AppProviders.js +9 -2
  174. package/dist/app/providers/AppProviders.js.map +1 -1
  175. package/dist/app/providers/BuilderSignupActivation.d.ts +2 -1
  176. package/dist/app/providers/BuilderSignupActivation.d.ts.map +1 -1
  177. package/dist/app/providers/BuilderSignupActivation.js +6 -3
  178. package/dist/app/providers/BuilderSignupActivation.js.map +1 -1
  179. package/dist/app/shared/AppShellSkeleton.d.ts.map +1 -1
  180. package/dist/app/shared/AppShellSkeleton.js +11 -1
  181. package/dist/app/shared/AppShellSkeleton.js.map +1 -1
  182. package/dist/app/styles/agent-native.css +2 -2
  183. package/dist/app/styles/tokens/agent-kit.css +20 -3
  184. package/dist/composer/AgentSuggestionBar.d.ts.map +1 -1
  185. package/dist/composer/AgentSuggestionBar.js +2 -2
  186. package/dist/composer/AgentSuggestionBar.js.map +1 -1
  187. package/dist/composer/ComposerContextMenu.js +4 -4
  188. package/dist/composer/ComposerContextMenu.js.map +1 -1
  189. package/dist/composer/ComposerContextPicker.d.ts.map +1 -1
  190. package/dist/composer/ComposerContextPicker.js +2 -2
  191. package/dist/composer/ComposerContextPicker.js.map +1 -1
  192. package/dist/composer/ComposerContextPickerDialog.d.ts.map +1 -1
  193. package/dist/composer/ComposerContextPickerDialog.js +2 -2
  194. package/dist/composer/ComposerContextPickerDialog.js.map +1 -1
  195. package/dist/composer/ComposerModePicker.d.ts.map +1 -1
  196. package/dist/composer/ComposerModePicker.js +2 -1
  197. package/dist/composer/ComposerModePicker.js.map +1 -1
  198. package/dist/composer/ComposerPlusMenu.js +1 -1
  199. package/dist/composer/ComposerPlusMenu.js.map +1 -1
  200. package/dist/composer/MentionChip.js +1 -1
  201. package/dist/composer/MentionChip.js.map +1 -1
  202. package/dist/composer/MentionPopover.d.ts +3 -0
  203. package/dist/composer/MentionPopover.d.ts.map +1 -1
  204. package/dist/composer/MentionPopover.js +34 -15
  205. package/dist/composer/MentionPopover.js.map +1 -1
  206. package/dist/composer/MessageQueueDrawer.d.ts.map +1 -1
  207. package/dist/composer/MessageQueueDrawer.js +14 -9
  208. package/dist/composer/MessageQueueDrawer.js.map +1 -1
  209. package/dist/composer/PastedTextChip.d.ts.map +1 -1
  210. package/dist/composer/PastedTextChip.js +14 -18
  211. package/dist/composer/PastedTextChip.js.map +1 -1
  212. package/dist/composer/PromptBar.js +1 -1
  213. package/dist/composer/PromptBar.js.map +1 -1
  214. package/dist/composer/PromptComposer.d.ts.map +1 -1
  215. package/dist/composer/PromptComposer.js +11 -9
  216. package/dist/composer/PromptComposer.js.map +1 -1
  217. package/dist/composer/RealtimeVoiceMode.js +2 -2
  218. package/dist/composer/RealtimeVoiceMode.js.map +1 -1
  219. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  220. package/dist/composer/TiptapComposer.js +52 -89
  221. package/dist/composer/TiptapComposer.js.map +1 -1
  222. package/dist/composer/VoiceButton.d.ts +3 -1
  223. package/dist/composer/VoiceButton.d.ts.map +1 -1
  224. package/dist/composer/VoiceButton.js +10 -8
  225. package/dist/composer/VoiceButton.js.map +1 -1
  226. package/dist/composer/composer-notice.js +3 -3
  227. package/dist/composer/composer-notice.js.map +1 -1
  228. package/dist/composer/extensions/FileReference.js +1 -1
  229. package/dist/composer/extensions/FileReference.js.map +1 -1
  230. package/dist/composer/extensions/SkillReference.js +1 -1
  231. package/dist/composer/extensions/SkillReference.js.map +1 -1
  232. package/dist/composer/popover-surface.d.ts +1 -1
  233. package/dist/composer/popover-surface.d.ts.map +1 -1
  234. package/dist/composer/popover-surface.js +1 -1
  235. package/dist/composer/popover-surface.js.map +1 -1
  236. package/dist/context-ui/ContextUsageRing.d.ts +1 -45
  237. package/dist/context-ui/ContextUsageRing.d.ts.map +1 -1
  238. package/dist/context-ui/ContextUsageRing.js +2 -87
  239. package/dist/context-ui/ContextUsageRing.js.map +1 -1
  240. package/dist/context-ui/index.d.ts +2 -2
  241. package/dist/context-ui/index.d.ts.map +1 -1
  242. package/dist/context-ui/index.js +2 -2
  243. package/dist/context-ui/index.js.map +1 -1
  244. package/package.json +4 -4
  245. package/src/app/agentkit/react/approval-card.tsx +14 -2
  246. package/src/app/agentkit/react/code-block.spec.tsx +154 -0
  247. package/src/app/agentkit/react/code-block.tsx +317 -0
  248. package/src/app/agentkit/react/components.interaction.spec.tsx +7 -0
  249. package/src/app/agentkit/react/components.papercuts.spec.tsx +143 -2
  250. package/src/app/agentkit/react/components.spec.tsx +40 -47
  251. package/src/app/agentkit/react/components.tsx +1533 -413
  252. package/src/app/agentkit/react/composer-context.spec.tsx +35 -0
  253. package/src/app/agentkit/react/context-usage.tsx +353 -0
  254. package/src/app/agentkit/react/context.tsx +93 -1
  255. package/src/app/agentkit/react/disclosure-content.ts +42 -0
  256. package/src/app/agentkit/react/disclosures.spec.tsx +62 -4
  257. package/src/app/agentkit/react/duration.ts +22 -0
  258. package/src/app/agentkit/react/file-change.spec.tsx +124 -0
  259. package/src/app/agentkit/react/file-change.tsx +228 -0
  260. package/src/app/agentkit/react/file-diff.ts +110 -0
  261. package/src/app/agentkit/react/index.ts +7 -0
  262. package/src/app/agentkit/react/lifecycle.spec.tsx +565 -20
  263. package/src/app/agentkit/react/markdown.spec.tsx +18 -8
  264. package/src/app/agentkit/react/message-actions.spec.tsx +55 -1
  265. package/src/app/agentkit/react/notices.spec.tsx +4 -4
  266. package/src/app/agentkit/react/optimistic-send.spec.tsx +43 -0
  267. package/src/app/agentkit/react/question-card.spec.tsx +276 -0
  268. package/src/app/agentkit/react/question-card.tsx +533 -0
  269. package/src/app/agentkit/react/root.tsx +4 -0
  270. package/src/app/agentkit/react/roving-focus.spec.tsx +114 -0
  271. package/src/app/agentkit/react/roving-focus.ts +82 -0
  272. package/src/app/agentkit/react/stack-trace.spec.tsx +177 -0
  273. package/src/app/agentkit/react/stack-trace.tsx +281 -0
  274. package/src/app/agentkit/react/stop-button.spec.tsx +40 -0
  275. package/src/app/agentkit/react/stop-button.tsx +22 -5
  276. package/src/app/agentkit/react/styles.css +1306 -395
  277. package/src/app/agentkit/react/subagent-step.spec.tsx +554 -0
  278. package/src/app/agentkit/react/terminal.spec.tsx +228 -0
  279. package/src/app/agentkit/react/terminal.tsx +228 -0
  280. package/src/app/agentkit/react/trace.spec.tsx +120 -0
  281. package/src/app/agentkit/react/trace.tsx +239 -0
  282. package/src/app/blocks/library/HighlightedCode.tsx +14 -126
  283. package/src/app/chat/AgentKitAssistantChat.spec.tsx +28 -12
  284. package/src/app/chat/AgentKitAssistantChat.tsx +27 -253
  285. package/src/app/chat/agentkit-chat/approval-card.spec.tsx +1 -0
  286. package/src/app/chat/agentkit-chat/guided-questions.flow.spec.tsx +381 -0
  287. package/src/app/chat/agentkit-chat/guided-questions.tsx +236 -3
  288. package/src/app/chat/agentkit-chat/history.tsx +1 -1
  289. package/src/app/chat/agentkit-chat/index.ts +2 -0
  290. package/src/app/chat/agentkit-chat/labels.ts +259 -0
  291. package/src/app/chat/agentkit-chat/questions.ts +2 -0
  292. package/src/app/chat/agentkit-chat/root.spec.tsx +18 -0
  293. package/src/app/chat/agentkit-chat/root.tsx +5 -64
  294. package/src/app/chat/agentkit-chat/work-step-labels.ts +42 -0
  295. package/src/app/chat/chat/markdown-renderer.tsx +2 -59
  296. package/src/app/chat/chat/tool-call-display.spec.tsx +26 -14
  297. package/src/app/chat/chat/tool-call-display.tsx +2 -1
  298. package/src/app/chat/conversation/AgentConversation.tsx +2 -50
  299. package/src/app/chat/tool-cells/BashCell.tsx +14 -136
  300. package/src/app/chat/tool-cells/EditCell.tsx +11 -248
  301. package/src/app/chat/tool-cells/FilesChangedSummary.spec.tsx +62 -5
  302. package/src/app/chat/tool-cells/FilesChangedSummary.tsx +31 -162
  303. package/src/app/chat/tool-cells/WriteCell.tsx +11 -121
  304. package/src/app/code-highlighter.ts +131 -0
  305. package/src/app/context-xray/ThreadContextRing.tsx +7 -7
  306. package/src/app/i18n/catalogs/ar-SA.ts +22 -0
  307. package/src/app/i18n/catalogs/de-DE.ts +22 -0
  308. package/src/app/i18n/catalogs/en-US.ts +22 -0
  309. package/src/app/i18n/catalogs/es-ES.ts +22 -0
  310. package/src/app/i18n/catalogs/fr-FR.ts +22 -0
  311. package/src/app/i18n/catalogs/hi-IN.ts +22 -0
  312. package/src/app/i18n/catalogs/ja-JP.ts +22 -0
  313. package/src/app/i18n/catalogs/ko-KR.ts +22 -0
  314. package/src/app/i18n/catalogs/pt-BR.ts +22 -0
  315. package/src/app/i18n/catalogs/zh-CN.ts +22 -0
  316. package/src/app/i18n/catalogs/zh-TW.ts +22 -0
  317. package/src/app/onboarding/first-run-first-paint.spec.ts +39 -0
  318. package/src/app/onboarding/first-run-first-paint.ts +24 -0
  319. package/src/app/onboarding/first-run-startup-gate.spec.tsx +125 -13
  320. package/src/app/onboarding/first-run-startup-gate.tsx +48 -5
  321. package/src/app/providers/AppProviders.tsx +18 -0
  322. package/src/app/providers/BuilderSignupActivation.tsx +8 -2
  323. package/src/app/shared/AppShellSkeleton.spec.tsx +60 -1
  324. package/src/app/shared/AppShellSkeleton.tsx +31 -1
  325. package/src/app/styles/agent-native.css +2 -2
  326. package/src/app/styles/agent-native.spec.ts +2 -2
  327. package/src/app/styles/tokens/agent-kit.css +20 -3
  328. package/src/composer/AgentSuggestionBar.spec.tsx +3 -1
  329. package/src/composer/AgentSuggestionBar.tsx +5 -2
  330. package/src/composer/ComposerContextMenu.tsx +4 -4
  331. package/src/composer/ComposerContextPicker.tsx +8 -2
  332. package/src/composer/ComposerContextPickerDialog.tsx +5 -2
  333. package/src/composer/ComposerModePicker.tsx +2 -1
  334. package/src/composer/ComposerPlusMenu.tsx +1 -1
  335. package/src/composer/MentionChip.tsx +1 -1
  336. package/src/composer/MentionPopover.spec.tsx +108 -2
  337. package/src/composer/MentionPopover.tsx +93 -17
  338. package/src/composer/MessageQueueDrawer.spec.tsx +5 -5
  339. package/src/composer/MessageQueueDrawer.tsx +19 -12
  340. package/src/composer/PastedTextChip.spec.tsx +36 -0
  341. package/src/composer/PastedTextChip.tsx +52 -58
  342. package/src/composer/PromptBar.tsx +1 -1
  343. package/src/composer/PromptComposer.context.spec.tsx +2 -2
  344. package/src/composer/PromptComposer.submission.spec.tsx +27 -0
  345. package/src/composer/PromptComposer.tsx +17 -9
  346. package/src/composer/RealtimeVoiceMode.tsx +2 -2
  347. package/src/composer/TiptapComposer.model-picker.spec.tsx +23 -82
  348. package/src/composer/TiptapComposer.tsx +68 -145
  349. package/src/composer/VoiceButton.tsx +12 -7
  350. package/src/composer/composer-notice.tsx +3 -3
  351. package/src/composer/extensions/FileReference.tsx +1 -1
  352. package/src/composer/extensions/SkillReference.tsx +1 -1
  353. package/src/composer/popover-surface.ts +1 -1
  354. package/src/context-ui/ContextUsageRing.spec.tsx +33 -8
  355. package/src/context-ui/ContextUsageRing.tsx +13 -348
  356. package/src/context-ui/index.ts +7 -1
  357. package/dist/composer/ModelPickerParts.d.ts +0 -20
  358. package/dist/composer/ModelPickerParts.d.ts.map +0 -1
  359. package/dist/composer/ModelPickerParts.js +0 -77
  360. package/dist/composer/ModelPickerParts.js.map +0 -1
  361. package/src/composer/ModelPickerParts.tsx +0 -188
@@ -0,0 +1,82 @@
1
+ import { useEffect, type RefObject } from "react";
2
+
3
+ const ROVING_CANDIDATES = "button, a[href]";
4
+
5
+ function rovingItems(container: HTMLElement): HTMLElement[] {
6
+ return Array.from(
7
+ container.querySelectorAll<HTMLElement>(ROVING_CANDIDATES),
8
+ ).filter(
9
+ (item) =>
10
+ !(item instanceof HTMLButtonElement && item.disabled) &&
11
+ // An inert subtree takes no focus, so it gets no tab stop either.
12
+ !item.closest("[inert]"),
13
+ );
14
+ }
15
+
16
+ /** One tab stop for `container`, landing on the last-used item; arrows move. */
17
+ export function useRovingFocus(
18
+ containerRef: RefObject<HTMLElement | null>,
19
+ enabled = true,
20
+ ): void {
21
+ useEffect(() => {
22
+ const container = containerRef.current;
23
+ if (!container || !enabled) return;
24
+ let current: HTMLElement | null = null;
25
+ const sync = () => {
26
+ const items = rovingItems(container);
27
+ if (!current || !items.includes(current)) current = items[0] ?? null;
28
+ // Every candidate, so a newly enabled item joins at -1, not as a stop.
29
+ for (const item of container.querySelectorAll<HTMLElement>(
30
+ ROVING_CANDIDATES,
31
+ ))
32
+ item.tabIndex = item === current ? 0 : -1;
33
+ };
34
+ const onFocusIn = (event: FocusEvent) => {
35
+ const target = event.target;
36
+ if (!(target instanceof HTMLElement)) return;
37
+ if (!rovingItems(container).includes(target)) return;
38
+ current = target;
39
+ sync();
40
+ };
41
+ const onKeyDown = (event: KeyboardEvent) => {
42
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
43
+ const items = rovingItems(container);
44
+ const index = items.indexOf(document.activeElement as HTMLElement);
45
+ if (index < 0) return;
46
+ const rtl =
47
+ getComputedStyle(container).direction === "rtl" ||
48
+ container.closest("[dir]")?.getAttribute("dir") === "rtl";
49
+ const forward = rtl ? "ArrowLeft" : "ArrowRight";
50
+ const backward = rtl ? "ArrowRight" : "ArrowLeft";
51
+ const next =
52
+ event.key === forward
53
+ ? items[(index + 1) % items.length]
54
+ : event.key === backward
55
+ ? items[(index - 1 + items.length) % items.length]
56
+ : event.key === "Home"
57
+ ? items[0]
58
+ : event.key === "End"
59
+ ? items[items.length - 1]
60
+ : undefined;
61
+ if (!next) return;
62
+ event.preventDefault();
63
+ next.focus();
64
+ };
65
+ sync();
66
+ // Items and their disabled/inert state change without a re-render.
67
+ const observer = new MutationObserver(sync);
68
+ observer.observe(container, {
69
+ subtree: true,
70
+ childList: true,
71
+ attributes: true,
72
+ attributeFilter: ["disabled", "inert"],
73
+ });
74
+ container.addEventListener("focusin", onFocusIn);
75
+ container.addEventListener("keydown", onKeyDown);
76
+ return () => {
77
+ observer.disconnect();
78
+ container.removeEventListener("focusin", onFocusIn);
79
+ container.removeEventListener("keydown", onKeyDown);
80
+ };
81
+ }, [containerRef, enabled]);
82
+ }
@@ -0,0 +1,177 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import { act } from "react";
4
+ import { createRoot, type Root } from "react-dom/client";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+
7
+ import { parseStackTrace, StackTrace, type StackFrame } from "./stack-trace.js";
8
+
9
+ const NODE_CRASH = [
10
+ "stdout:",
11
+ "starting",
12
+ "",
13
+ "stderr:",
14
+ "file:///tmp/run/sandbox.mjs:1",
15
+ "function f(){ return x.y }",
16
+ " ^",
17
+ "",
18
+ "ReferenceError: x is not defined",
19
+ " at f (file:///tmp/run/sandbox.mjs:1:15)",
20
+ " at file:///tmp/run/sandbox.mjs:2:1",
21
+ " at ModuleJob.run (node:internal/modules/esm/module_job:569:25)",
22
+ " at async node:internal/modules/esm/loader:650:26",
23
+ " at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:105:5)",
24
+ "",
25
+ "Node.js v26.12.1",
26
+ ].join("\n");
27
+
28
+ const PYTHON_CRASH = [
29
+ "Traceback (most recent call last):",
30
+ ' File "/work/app.py", line 4, in <module>',
31
+ " main()",
32
+ ' File "/usr/lib/python3.12/json/__init__.py", line 346, in loads',
33
+ " return _default_decoder.decode(s)",
34
+ "json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)",
35
+ ].join("\n");
36
+
37
+ describe("parseStackTrace", () => {
38
+ it("finds a Node stack inside other output", () => {
39
+ const trace = parseStackTrace(NODE_CRASH)!;
40
+ expect(trace.error).toBe("ReferenceError: x is not defined");
41
+ expect(trace.whole).toBe(false);
42
+ expect(trace.text.split("\n")).toHaveLength(6);
43
+ expect(trace.frames[0]).toEqual({
44
+ text: "at f (file:///tmp/run/sandbox.mjs:1:15)",
45
+ name: "f",
46
+ file: "file:///tmp/run/sandbox.mjs",
47
+ line: 1,
48
+ column: 15,
49
+ internal: false,
50
+ });
51
+ expect(trace.frames[1]).toMatchObject({
52
+ file: "file:///tmp/run/sandbox.mjs",
53
+ line: 2,
54
+ internal: false,
55
+ });
56
+ expect(trace.frames[1]?.name).toBeUndefined();
57
+ expect(trace.frames.slice(2).map((frame) => frame.internal)).toEqual([
58
+ true,
59
+ true,
60
+ true,
61
+ ]);
62
+ expect(trace.frames[4]?.name).toBe("asyncRunEntryPointWithESMLoader");
63
+ });
64
+
65
+ it("reads a Python traceback, marking the standard library", () => {
66
+ const trace = parseStackTrace(PYTHON_CRASH)!;
67
+ expect(trace.error).toBe(
68
+ "json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)",
69
+ );
70
+ expect(trace.whole).toBe(true);
71
+ expect(
72
+ trace.frames.map((frame) => [frame.file, frame.line, frame.internal]),
73
+ ).toEqual([
74
+ ["/work/app.py", 4, false],
75
+ ["/usr/lib/python3.12/json/__init__.py", 346, true],
76
+ ]);
77
+ });
78
+
79
+ it("finds nothing in output without frames", () => {
80
+ expect(parseStackTrace("stdout:\nok\n\nstderr:\ndone")).toBeUndefined();
81
+ expect(parseStackTrace("Unhandled error: boom")).toBeUndefined();
82
+ });
83
+ });
84
+
85
+ let root: Root;
86
+ let container: HTMLDivElement;
87
+
88
+ beforeEach(() => {
89
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
90
+ container = document.createElement("div");
91
+ document.body.append(container);
92
+ root = createRoot(container);
93
+ });
94
+
95
+ afterEach(() => {
96
+ act(() => root.unmount());
97
+ container.remove();
98
+ vi.unstubAllGlobals();
99
+ });
100
+
101
+ describe("StackTrace", () => {
102
+ it("titles the error and folds the framework frames after the caller's", async () => {
103
+ const opened: StackFrame[] = [];
104
+ await act(async () =>
105
+ root.render(
106
+ <StackTrace
107
+ trace={NODE_CRASH}
108
+ defaultOpen
109
+ onFilePathClick={(frame) => opened.push(frame)}
110
+ />,
111
+ ),
112
+ );
113
+ const trigger = container.querySelector<HTMLButtonElement>(
114
+ "button.agentkit-disclosure-summary",
115
+ )!;
116
+ expect(trigger.textContent).toBe("ReferenceError: x is not defined");
117
+ expect(trigger.getAttribute("aria-expanded")).toBe("true");
118
+ expect(
119
+ container
120
+ .querySelector('[data-slot="trace-line"]')
121
+ ?.getAttribute("data-trace-status"),
122
+ ).toBe("error");
123
+
124
+ const own = Array.from(
125
+ container.querySelectorAll(
126
+ ".agentkit-stack-trace-body > ol > .agentkit-stack-frame",
127
+ ),
128
+ (frame) => frame.textContent,
129
+ );
130
+ expect(own).toEqual(["fsandbox.mjs:1:15", "sandbox.mjs:2:1"]);
131
+ expect(
132
+ container
133
+ .querySelector(".agentkit-stack-frame-location")
134
+ ?.getAttribute("title"),
135
+ ).toBe("file:///tmp/run/sandbox.mjs:1:15");
136
+ const folded = container.querySelector(".agentkit-stack-frames-folded");
137
+ expect(folded?.querySelector("button")?.textContent).toBe(
138
+ "3 framework frames",
139
+ );
140
+ // Folded frames stay findable in the page.
141
+ expect(
142
+ folded
143
+ ?.querySelector('[data-slot="trace-collapsible-content"]')
144
+ ?.getAttribute("hidden"),
145
+ ).toBe("until-found");
146
+
147
+ await act(async () =>
148
+ container
149
+ .querySelector<HTMLButtonElement>(
150
+ "button.agentkit-stack-frame-location",
151
+ )
152
+ ?.click(),
153
+ );
154
+ expect(opened.map((frame) => frame.line)).toEqual([1]);
155
+ });
156
+
157
+ it("copies only the trace", async () => {
158
+ const writeText = vi.fn(async () => undefined);
159
+ vi.stubGlobal("navigator", { ...navigator, clipboard: { writeText } });
160
+ await act(async () => root.render(<StackTrace trace={NODE_CRASH} />));
161
+ const copy = container.querySelector<HTMLButtonElement>(
162
+ ".agentkit-stack-trace-actions button",
163
+ )!;
164
+ expect(copy.getAttribute("aria-label")).toBe("Copy stack trace");
165
+ await act(async () => {
166
+ copy.click();
167
+ await Promise.resolve();
168
+ await Promise.resolve();
169
+ });
170
+ expect(writeText).toHaveBeenCalledWith(parseStackTrace(NODE_CRASH)!.text);
171
+ });
172
+
173
+ it("renders nothing for text without a stack", async () => {
174
+ await act(async () => root.render(<StackTrace trace="all good" />));
175
+ expect(container.innerHTML).toBe("");
176
+ });
177
+ });
@@ -0,0 +1,281 @@
1
+ import { IconAlertTriangle } from "@tabler/icons-react";
2
+
3
+ import { CodeBlockCopyButton } from "./code-block.js";
4
+ import { useAgentKitLabels } from "./context.js";
5
+ import {
6
+ Trace,
7
+ TraceCollapsible,
8
+ TraceCollapsibleContent,
9
+ TraceCollapsibleTrigger,
10
+ TraceIcon,
11
+ TraceLine,
12
+ TraceTitle,
13
+ type TraceCollapsibleOpenProps,
14
+ } from "./trace.js";
15
+
16
+ export interface StackFrame {
17
+ text: string;
18
+ name?: string;
19
+ /** Without the line and column. */
20
+ file?: string;
21
+ line?: number;
22
+ column?: number;
23
+ /** Runtime, dependency, or standard library code rather than the caller's. */
24
+ internal: boolean;
25
+ }
26
+
27
+ export interface ParsedStackTrace {
28
+ error: string;
29
+ frames: StackFrame[];
30
+ /** The trace exactly as printed, for copying. */
31
+ text: string;
32
+ /** Whether the text holds nothing but the trace. */
33
+ whole: boolean;
34
+ }
35
+
36
+ const V8_FRAME = /^\s+at\s+(.+)$/;
37
+ const PYTHON_HEADER = /^Traceback \(most recent call last\):\s*$/;
38
+ const PYTHON_FRAME = /^\s+File "([^"]+)", line (\d+)(?:, in (.+))?$/;
39
+ const INTERNAL_FILE =
40
+ /(?:^|[/\\])node_modules[/\\]|^node:|^internal[/\\]|[/\\]site-packages[/\\]|[/\\]lib[/\\]python\d|^<frozen /;
41
+
42
+ /** The first V8 or Python trace in `text`, without the output around it. */
43
+ export function parseStackTrace(text: string): ParsedStackTrace | undefined {
44
+ const lines = text.split("\n");
45
+ return parseV8(lines) ?? parsePython(lines);
46
+ }
47
+
48
+ function parseV8(lines: string[]): ParsedStackTrace | undefined {
49
+ const first = lines.findIndex((line) => V8_FRAME.test(line));
50
+ const error = lines[first - 1]?.trim();
51
+ if (first < 1 || !error) return undefined;
52
+ const frames: StackFrame[] = [];
53
+ for (const line of lines.slice(first)) {
54
+ const body = V8_FRAME.exec(line)?.[1];
55
+ if (body === undefined) break;
56
+ frames.push(v8Frame(body.trim()));
57
+ }
58
+ return result(lines, first - 1, first + frames.length, error, frames);
59
+ }
60
+
61
+ function v8Frame(body: string): StackFrame {
62
+ const named = /^(.*?) \((.+)\)$/.exec(body);
63
+ const name = group(named, 1)?.replace(/^async /, "") || undefined;
64
+ const location = group(named, 2) ?? body.replace(/^async /, "");
65
+ const position = /^(.*?):(\d+):(\d+)$/.exec(location);
66
+ const file = group(position, 1) ?? location;
67
+ const line = group(position, 2);
68
+ const column = group(position, 3);
69
+ return {
70
+ text: `at ${body}`,
71
+ ...(name ? { name } : {}),
72
+ file,
73
+ ...(line && column ? { line: Number(line), column: Number(column) } : {}),
74
+ internal: INTERNAL_FILE.test(file),
75
+ };
76
+ }
77
+
78
+ function group(
79
+ match: RegExpExecArray | null,
80
+ index: number,
81
+ ): string | undefined {
82
+ return match?.[index];
83
+ }
84
+
85
+ function parsePython(lines: string[]): ParsedStackTrace | undefined {
86
+ const start = lines.findIndex((line) => PYTHON_HEADER.test(line));
87
+ if (start < 0) return undefined;
88
+ const frames: StackFrame[] = [];
89
+ let index = start + 1;
90
+ for (const line of lines.slice(start + 1)) {
91
+ const [, file, number, name] = PYTHON_FRAME.exec(line) ?? [];
92
+ if (file && number) {
93
+ frames.push({
94
+ text: line.trim(),
95
+ ...(name ? { name } : {}),
96
+ file,
97
+ line: Number(number),
98
+ internal: INTERNAL_FILE.test(file),
99
+ });
100
+ } else if (!/^\s/.test(line)) {
101
+ break;
102
+ }
103
+ index++;
104
+ }
105
+ const error = lines[index]?.trim();
106
+ if (!error || !frames.length) return undefined;
107
+ return result(lines, start, index + 1, error, frames);
108
+ }
109
+
110
+ function result(
111
+ lines: string[],
112
+ start: number,
113
+ end: number,
114
+ error: string,
115
+ frames: StackFrame[],
116
+ ): ParsedStackTrace {
117
+ const outside = [...lines.slice(0, start), ...lines.slice(end)];
118
+ return {
119
+ error,
120
+ frames,
121
+ text: lines.slice(start, end).join("\n"),
122
+ whole: outside.every((line) => !line.trim()),
123
+ };
124
+ }
125
+
126
+ /** Copy sits beside the trigger: a button cannot nest in the trigger. */
127
+ export function StackTrace({
128
+ trace,
129
+ defaultOpen,
130
+ onFilePathClick,
131
+ className,
132
+ ...openProps
133
+ }: TraceCollapsibleOpenProps & {
134
+ trace: string | ParsedStackTrace;
135
+ defaultOpen?: boolean;
136
+ /** Makes the caller's frames links to their file and line. */
137
+ onFilePathClick?: (frame: StackFrame) => void;
138
+ className?: string;
139
+ }) {
140
+ const labels = useAgentKitLabels();
141
+ const parsed = typeof trace === "string" ? parseStackTrace(trace) : trace;
142
+ if (!parsed) return null;
143
+ return (
144
+ <Trace
145
+ data-slot="stack-trace"
146
+ className={
147
+ className ? `agentkit-stack-trace ${className}` : "agentkit-stack-trace"
148
+ }
149
+ >
150
+ <TraceCollapsible {...openProps} defaultOpen={defaultOpen}>
151
+ <div className="agentkit-stack-trace-row">
152
+ <TraceCollapsibleTrigger>
153
+ <TraceLine status="error">
154
+ <TraceIcon>
155
+ <IconAlertTriangle
156
+ aria-hidden="true"
157
+ className="agentkit-icon"
158
+ />
159
+ </TraceIcon>
160
+ <TraceTitle>{parsed.error}</TraceTitle>
161
+ </TraceLine>
162
+ </TraceCollapsibleTrigger>
163
+ <span className="agentkit-stack-trace-actions">
164
+ <CodeBlockCopyButton
165
+ value={parsed.text}
166
+ label={labels.workStepCopyStackTrace}
167
+ />
168
+ </span>
169
+ </div>
170
+ <TraceCollapsibleContent
171
+ keepMounted
172
+ className="agentkit-stack-trace-body"
173
+ >
174
+ {/* The title truncates, so the full error leads the frames. */}
175
+ <p className="agentkit-stack-trace-error">{parsed.error}</p>
176
+ <StackTraceFrames
177
+ frames={parsed.frames}
178
+ onFilePathClick={onFilePathClick}
179
+ />
180
+ </TraceCollapsibleContent>
181
+ </TraceCollapsible>
182
+ </Trace>
183
+ );
184
+ }
185
+
186
+ export function StackTraceFrames({
187
+ frames,
188
+ onFilePathClick,
189
+ }: {
190
+ frames: StackFrame[];
191
+ onFilePathClick?: ((frame: StackFrame) => void) | undefined;
192
+ }) {
193
+ const labels = useAgentKitLabels();
194
+ const runs: Array<{
195
+ start: number;
196
+ internal: boolean;
197
+ frames: StackFrame[];
198
+ }> = [];
199
+ frames.forEach((frame, index) => {
200
+ const last = runs.at(-1);
201
+ if (last?.internal && frame.internal) last.frames.push(frame);
202
+ else runs.push({ start: index, internal: frame.internal, frames: [frame] });
203
+ });
204
+ const items = runs.map(({ start, frames: run }) => {
205
+ if (run.length < 2) {
206
+ return run.map((frame) => (
207
+ <StackTraceFrame
208
+ key={start}
209
+ frame={frame}
210
+ onFilePathClick={onFilePathClick}
211
+ />
212
+ ));
213
+ }
214
+ return (
215
+ <li key={start} className="agentkit-stack-frames-folded">
216
+ <TraceCollapsible>
217
+ <TraceCollapsibleTrigger>
218
+ <TraceLine>
219
+ <span className="agentkit-disclosure-label">
220
+ {labels.workStepFrameworkFrames.replace(
221
+ "{{count}}",
222
+ () => `${run.length}`,
223
+ )}
224
+ </span>
225
+ </TraceLine>
226
+ </TraceCollapsibleTrigger>
227
+ <TraceCollapsibleContent keepMounted>
228
+ <ol className="agentkit-stack-frames">
229
+ {run.map((frame, offset) => (
230
+ <StackTraceFrame key={start + offset} frame={frame} />
231
+ ))}
232
+ </ol>
233
+ </TraceCollapsibleContent>
234
+ </TraceCollapsible>
235
+ </li>
236
+ );
237
+ });
238
+ return <ol className="agentkit-stack-frames">{items}</ol>;
239
+ }
240
+
241
+ function StackTraceFrame({
242
+ frame,
243
+ onFilePathClick,
244
+ }: {
245
+ frame: StackFrame;
246
+ onFilePathClick?: ((frame: StackFrame) => void) | undefined;
247
+ }) {
248
+ const position =
249
+ frame.line !== undefined
250
+ ? `:${frame.line}${frame.column !== undefined ? `:${frame.column}` : ""}`
251
+ : "";
252
+ // The file name only, so a long path never hides the line number.
253
+ const location = frame.file
254
+ ? `${frame.file.split(/[/\\]/).at(-1) || frame.file}${position}`
255
+ : frame.text;
256
+ const fullLocation = frame.file ? `${frame.file}${position}` : frame.text;
257
+ return (
258
+ <li
259
+ className="agentkit-stack-frame"
260
+ data-internal={frame.internal ? "true" : undefined}
261
+ >
262
+ {frame.name ? (
263
+ <span className="agentkit-stack-frame-name">{frame.name}</span>
264
+ ) : null}
265
+ {onFilePathClick && frame.file && !frame.internal ? (
266
+ <button
267
+ type="button"
268
+ className="agentkit-stack-frame-location"
269
+ title={fullLocation}
270
+ onClick={() => onFilePathClick(frame)}
271
+ >
272
+ {location}
273
+ </button>
274
+ ) : (
275
+ <span className="agentkit-stack-frame-location" title={fullLocation}>
276
+ {location}
277
+ </span>
278
+ )}
279
+ </li>
280
+ );
281
+ }
@@ -14,9 +14,14 @@ const mocks = vi.hoisted(() => ({
14
14
  activeRunIds: [] as string[],
15
15
  runs: {} as Record<string, { status: string }>,
16
16
  cancel: vi.fn(async () => undefined),
17
+ requestComposerFocus: vi.fn(),
17
18
  }));
18
19
 
19
20
  vi.mock("./context.js", () => ({
21
+ useAgentKit: () => ({
22
+ requestComposerFocus: mocks.requestComposerFocus,
23
+ threadId: "thread-1",
24
+ }),
20
25
  useAgentThread: () => ({
21
26
  activeRunIds: mocks.activeRunIds,
22
27
  runs: mocks.runs,
@@ -48,6 +53,7 @@ describe("useAgentKitStopButton", () => {
48
53
  mocks.cancel.mockReset();
49
54
  mocks.cancel.mockResolvedValue(undefined);
50
55
  onError.mockClear();
56
+ mocks.requestComposerFocus.mockClear();
51
57
  beforeCancel = undefined;
52
58
  });
53
59
 
@@ -249,6 +255,40 @@ describe("useAgentKitStopButton", () => {
249
255
  await act(async () => cancelled.resolve(undefined));
250
256
  });
251
257
 
258
+ it.each([
259
+ ["keyboard", 0, 1],
260
+ ["pointer", 1, 0],
261
+ ] as const)(
262
+ "after a %s stop, hands focus to the composer only for the keyboard",
263
+ async (_input, detail, focusCalls) => {
264
+ mocks.activeRunIds = ["run-1"];
265
+ await render();
266
+ await act(async () => {
267
+ container
268
+ .querySelector('[data-agent-composer-slot="stop-button"]')
269
+ ?.dispatchEvent(new MouseEvent("click", { bubbles: true, detail }));
270
+ });
271
+ mocks.activeRunIds = [];
272
+ await act(async () => root.render(<Harness />));
273
+ expect(container.querySelector("button")).toBeNull();
274
+ expect(mocks.requestComposerFocus).toHaveBeenCalledTimes(focusCalls);
275
+ },
276
+ );
277
+
278
+ it("leaves focus alone when beforeCancel keeps the run going", async () => {
279
+ mocks.activeRunIds = ["run-1"];
280
+ beforeCancel = () => false;
281
+ await render();
282
+ await act(async () => {
283
+ container
284
+ .querySelector("button")
285
+ ?.dispatchEvent(new MouseEvent("click", { bubbles: true, detail: 0 }));
286
+ });
287
+ mocks.activeRunIds = [];
288
+ await act(async () => root.render(<Harness />));
289
+ expect(mocks.requestComposerFocus).not.toHaveBeenCalled();
290
+ });
291
+
252
292
  it("hides Stop once every retained run is terminal", async () => {
253
293
  mocks.activeRunIds = ["run-1"];
254
294
  mocks.runs = { "run-1": { status: "completed" } };
@@ -1,9 +1,10 @@
1
1
  import { hasActiveAgentRuns } from "@agent-native/agentkit";
2
2
  import { IconPlayerStopFilled } from "@tabler/icons-react";
3
- import { useRef, useState } from "react";
3
+ import { useEffect, useRef, useState } from "react";
4
4
 
5
5
  import { Tooltip } from "../../../design-system/index.js";
6
6
  import {
7
+ useAgentKit,
7
8
  useAgentKitControl,
8
9
  useAgentKitSubmissionStarting,
9
10
  useAgentThread,
@@ -53,7 +54,17 @@ export function useAgentKitStopButton(options: {
53
54
  onErrorRef.current = options.onError;
54
55
  const beforeCancelRef = useRef(options.beforeCancel);
55
56
  beforeCancelRef.current = options.beforeCancel;
56
- if (!running && !starting) return undefined;
57
+ const { requestComposerFocus, threadId: providerThreadId } = useAgentKit();
58
+ // Keyboard stops only: refocusing the composer on touch opens the keyboard.
59
+ const refocusComposerRef = useRef(false);
60
+ const visible = running || starting;
61
+ const focusThreadId = options.threadId ?? providerThreadId;
62
+ useEffect(() => {
63
+ if (visible || !refocusComposerRef.current) return;
64
+ refocusComposerRef.current = false;
65
+ requestComposerFocus(focusThreadId);
66
+ }, [focusThreadId, requestComposerFocus, visible]);
67
+ if (!visible) return undefined;
57
68
  const { label } = options;
58
69
  return (
59
70
  <Tooltip
@@ -61,8 +72,10 @@ export function useAgentKitStopButton(options: {
61
72
  trigger={
62
73
  <button
63
74
  type="button"
64
- onClick={() => {
75
+ onClick={(event) => {
65
76
  if (stoppingRef.current === activeRunKey) return;
77
+ // Enter and Space fire a click with no pointer detail.
78
+ refocusComposerRef.current = event.detail === 0;
66
79
  const runKey = activeRunKey;
67
80
  stoppingRef.current = runKey;
68
81
  setStoppingRunKey(runKey);
@@ -74,11 +87,15 @@ export function useAgentKitStopButton(options: {
74
87
  const knownRunIds = running ? thread.activeRunIds : undefined;
75
88
  void (async () => {
76
89
  const beforeCancel = beforeCancelRef.current;
77
- if (beforeCancel && (await beforeCancel()) === false) return;
90
+ if (beforeCancel && (await beforeCancel()) === false) {
91
+ refocusComposerRef.current = false;
92
+ return;
93
+ }
78
94
  const runIds = knownRunIds ?? (await control.runIdsOnceStarted());
79
95
  await Promise.all(runIds.map((runId) => control.cancel(runId)));
80
96
  })()
81
97
  .catch((error: unknown) => {
98
+ refocusComposerRef.current = false;
82
99
  onErrorRef.current(
83
100
  error instanceof Error ? error : new Error(String(error)),
84
101
  );
@@ -89,7 +106,7 @@ export function useAgentKitStopButton(options: {
89
106
  aria-busy={stopping || undefined}
90
107
  disabled={stopping}
91
108
  data-agent-composer-slot="stop-button"
92
- className={`flex size-[var(--agentkit-composer-primary-control-size,2rem)] shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground transition-[opacity,background-color,color] duration-150 hover:opacity-90 disabled:cursor-default disabled:opacity-35 ${STOP_BESIDE_PRIMARY}`}
109
+ className={`flex size-[var(--agentkit-composer-primary-control-size,2rem)] shrink-0 items-center justify-center rounded-[var(--agent-kit-radius-sm,0.5rem)] bg-primary text-primary-foreground transition-[opacity,background-color,color] duration-150 hover:opacity-90 disabled:cursor-default disabled:opacity-35 ${STOP_BESIDE_PRIMARY}`}
93
110
  >
94
111
  <IconPlayerStopFilled aria-hidden="true" className="size-4" />
95
112
  </button>