@agent-native/toolkit 0.221.0-same-host-mcp-20261027-20261027081850 → 0.221.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 (259) hide show
  1. package/agent-native.eject.json +16 -1
  2. package/dist/app/agentkit/react/components.d.ts +5 -3
  3. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  4. package/dist/app/agentkit/react/components.js +103 -42
  5. package/dist/app/agentkit/react/components.js.map +1 -1
  6. package/dist/app/agentkit/react/styles.css +93 -65
  7. package/dist/app/agentkit/react/voice-transcripts.d.ts +22 -1
  8. package/dist/app/agentkit/react/voice-transcripts.d.ts.map +1 -1
  9. package/dist/app/agentkit/react/voice-transcripts.js +209 -8
  10. package/dist/app/agentkit/react/voice-transcripts.js.map +1 -1
  11. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  12. package/dist/app/chat/AgentKitAssistantChat.js +55 -40
  13. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  14. package/dist/app/chat/agentkit-chat/guided-questions.d.ts +1 -0
  15. package/dist/app/chat/agentkit-chat/guided-questions.d.ts.map +1 -1
  16. package/dist/app/chat/agentkit-chat/guided-questions.js +18 -6
  17. package/dist/app/chat/agentkit-chat/guided-questions.js.map +1 -1
  18. package/dist/app/chat/agentkit-chat/index.d.ts +1 -1
  19. package/dist/app/chat/agentkit-chat/index.d.ts.map +1 -1
  20. package/dist/app/chat/agentkit-chat/index.js +1 -1
  21. package/dist/app/chat/agentkit-chat/index.js.map +1 -1
  22. package/dist/app/chat/chat/run-recovery.d.ts.map +1 -1
  23. package/dist/app/chat/chat/run-recovery.js +5 -1
  24. package/dist/app/chat/chat/run-recovery.js.map +1 -1
  25. package/dist/app/chat/composer/runtime-adapters.d.ts.map +1 -1
  26. package/dist/app/chat/composer/runtime-adapters.js +2 -0
  27. package/dist/app/chat/composer/runtime-adapters.js.map +1 -1
  28. package/dist/app/chat/reply-connection-suggestion.d.ts +29 -0
  29. package/dist/app/chat/reply-connection-suggestion.d.ts.map +1 -0
  30. package/dist/app/chat/reply-connection-suggestion.js +42 -0
  31. package/dist/app/chat/reply-connection-suggestion.js.map +1 -0
  32. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  33. package/dist/app/i18n/catalogs/ar-SA.js +7 -0
  34. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  35. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  36. package/dist/app/i18n/catalogs/de-DE.js +7 -0
  37. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  38. package/dist/app/i18n/catalogs/en-US.d.ts +7 -0
  39. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  40. package/dist/app/i18n/catalogs/en-US.js +7 -0
  41. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  42. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  43. package/dist/app/i18n/catalogs/es-ES.js +7 -0
  44. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  45. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  46. package/dist/app/i18n/catalogs/fr-FR.js +7 -0
  47. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  48. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  49. package/dist/app/i18n/catalogs/hi-IN.js +7 -0
  50. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  51. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  52. package/dist/app/i18n/catalogs/ja-JP.js +7 -0
  53. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  54. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  55. package/dist/app/i18n/catalogs/ko-KR.js +7 -0
  56. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  57. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  58. package/dist/app/i18n/catalogs/pt-BR.js +7 -0
  59. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  60. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  61. package/dist/app/i18n/catalogs/zh-CN.js +7 -0
  62. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  63. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  64. package/dist/app/i18n/catalogs/zh-TW.js +7 -0
  65. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  66. package/dist/app/mcp-approval/McpApprovalReview.d.ts +25 -0
  67. package/dist/app/mcp-approval/McpApprovalReview.d.ts.map +1 -0
  68. package/dist/app/mcp-approval/McpApprovalReview.js +342 -0
  69. package/dist/app/mcp-approval/McpApprovalReview.js.map +1 -0
  70. package/dist/app/onboarding/FirstRunOnboarding.d.ts.map +1 -1
  71. package/dist/app/onboarding/FirstRunOnboarding.js +299 -71
  72. package/dist/app/onboarding/FirstRunOnboarding.js.map +1 -1
  73. package/dist/app/onboarding/GettingStartedCard.js +1 -1
  74. package/dist/app/onboarding/GettingStartedCard.js.map +1 -1
  75. package/dist/app/onboarding/first-run-builder-credits-flag.d.ts +2 -0
  76. package/dist/app/onboarding/first-run-builder-credits-flag.d.ts.map +1 -0
  77. package/dist/app/onboarding/first-run-builder-credits-flag.js +8 -0
  78. package/dist/app/onboarding/first-run-builder-credits-flag.js.map +1 -0
  79. package/dist/app/providers/AppProviders.d.ts.map +1 -1
  80. package/dist/app/providers/AppProviders.js +14 -1
  81. package/dist/app/providers/AppProviders.js.map +1 -1
  82. package/dist/app/resources/McpConnectionSuggestion.d.ts +2 -4
  83. package/dist/app/resources/McpConnectionSuggestion.d.ts.map +1 -1
  84. package/dist/app/resources/McpConnectionSuggestion.js +59 -36
  85. package/dist/app/resources/McpConnectionSuggestion.js.map +1 -1
  86. package/dist/app/settings/account/PreferencesSettings.d.ts.map +1 -1
  87. package/dist/app/settings/account/PreferencesSettings.js +14 -1
  88. package/dist/app/settings/account/PreferencesSettings.js.map +1 -1
  89. package/dist/app/settings/account/ThemeFamilySettings.js +2 -2
  90. package/dist/app/settings/account/ThemeFamilySettings.js.map +1 -1
  91. package/dist/app/settings/account/search-entries.d.ts +1 -0
  92. package/dist/app/settings/account/search-entries.d.ts.map +1 -1
  93. package/dist/app/settings/account/search-entries.js +5 -0
  94. package/dist/app/settings/account/search-entries.js.map +1 -1
  95. package/dist/app/settings/shell/SettingsShell.d.ts.map +1 -1
  96. package/dist/app/settings/shell/SettingsShell.js +26 -2
  97. package/dist/app/settings/shell/SettingsShell.js.map +1 -1
  98. package/dist/app/shared/AppShellSkeleton.d.ts.map +1 -1
  99. package/dist/app/shared/AppShellSkeleton.js +4 -10
  100. package/dist/app/shared/AppShellSkeleton.js.map +1 -1
  101. package/dist/app/shared/EnvironmentBadge.d.ts +2 -1
  102. package/dist/app/shared/EnvironmentBadge.d.ts.map +1 -1
  103. package/dist/app/shared/EnvironmentBadge.js +2 -3
  104. package/dist/app/shared/EnvironmentBadge.js.map +1 -1
  105. package/dist/app/shared/is-builder-io-employee.d.ts +8 -0
  106. package/dist/app/shared/is-builder-io-employee.d.ts.map +1 -0
  107. package/dist/app/shared/is-builder-io-employee.js +10 -0
  108. package/dist/app/shared/is-builder-io-employee.js.map +1 -0
  109. package/dist/app/styles/agent-conversation.css +30 -17
  110. package/dist/app/styles/theme-families.css +22 -5
  111. package/dist/app/styles/tokens/agent-kit.css +3 -3
  112. package/dist/composer/PromptComposer.d.ts +15 -3
  113. package/dist/composer/PromptComposer.d.ts.map +1 -1
  114. package/dist/composer/PromptComposer.js +74 -16
  115. package/dist/composer/PromptComposer.js.map +1 -1
  116. package/dist/composer/RealtimeVoiceMode.d.ts +7 -0
  117. package/dist/composer/RealtimeVoiceMode.d.ts.map +1 -1
  118. package/dist/composer/RealtimeVoiceMode.js +9 -2
  119. package/dist/composer/RealtimeVoiceMode.js.map +1 -1
  120. package/dist/composer/TiptapComposer.d.ts +7 -0
  121. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  122. package/dist/composer/TiptapComposer.js +12 -4
  123. package/dist/composer/TiptapComposer.js.map +1 -1
  124. package/dist/composer/VoiceButton.d.ts +2 -0
  125. package/dist/composer/VoiceButton.d.ts.map +1 -1
  126. package/dist/composer/VoiceButton.js +5 -4
  127. package/dist/composer/VoiceButton.js.map +1 -1
  128. package/dist/composer/index.d.ts +2 -2
  129. package/dist/composer/index.d.ts.map +1 -1
  130. package/dist/composer/index.js +1 -1
  131. package/dist/composer/index.js.map +1 -1
  132. package/dist/composer/prompt-attachments.d.ts +20 -0
  133. package/dist/composer/prompt-attachments.d.ts.map +1 -1
  134. package/dist/composer/prompt-attachments.js +188 -11
  135. package/dist/composer/prompt-attachments.js.map +1 -1
  136. package/dist/composer/realtime-voice-transcript.d.ts +27 -0
  137. package/dist/composer/realtime-voice-transcript.d.ts.map +1 -1
  138. package/dist/composer/realtime-voice-transcript.js +34 -1
  139. package/dist/composer/realtime-voice-transcript.js.map +1 -1
  140. package/dist/composer/realtime-voice-turn-metrics.d.ts +43 -0
  141. package/dist/composer/realtime-voice-turn-metrics.d.ts.map +1 -0
  142. package/dist/composer/realtime-voice-turn-metrics.js +132 -0
  143. package/dist/composer/realtime-voice-turn-metrics.js.map +1 -0
  144. package/dist/composer/runtime-adapters.d.ts +4 -1
  145. package/dist/composer/runtime-adapters.d.ts.map +1 -1
  146. package/dist/composer/runtime-adapters.js +2 -1
  147. package/dist/composer/runtime-adapters.js.map +1 -1
  148. package/dist/composer/useRealtimeVoiceMode.d.ts +61 -9
  149. package/dist/composer/useRealtimeVoiceMode.d.ts.map +1 -1
  150. package/dist/composer/useRealtimeVoiceMode.js +536 -99
  151. package/dist/composer/useRealtimeVoiceMode.js.map +1 -1
  152. package/dist/composer/voice-chat-icon.d.ts +7 -0
  153. package/dist/composer/voice-chat-icon.d.ts.map +1 -0
  154. package/dist/composer/voice-chat-icon.js +14 -0
  155. package/dist/composer/voice-chat-icon.js.map +1 -0
  156. package/dist/editor/DragHandle.d.ts +12 -1
  157. package/dist/editor/DragHandle.d.ts.map +1 -1
  158. package/dist/editor/DragHandle.js +165 -41
  159. package/dist/editor/DragHandle.js.map +1 -1
  160. package/dist/editor/RegistryBlockNode.d.ts +20 -0
  161. package/dist/editor/RegistryBlockNode.d.ts.map +1 -1
  162. package/dist/editor/RegistryBlockNode.js +58 -13
  163. package/dist/editor/RegistryBlockNode.js.map +1 -1
  164. package/dist/editor/index.d.ts +2 -2
  165. package/dist/editor/index.d.ts.map +1 -1
  166. package/dist/editor/index.js +1 -1
  167. package/dist/editor/index.js.map +1 -1
  168. package/dist/styles.css +2 -0
  169. package/dist/ui/dropdown-menu.d.ts.map +1 -1
  170. package/dist/ui/dropdown-menu.js +5 -1
  171. package/dist/ui/dropdown-menu.js.map +1 -1
  172. package/package.json +14 -4
  173. package/src/app/agentkit/react/components.regenerate-readiness.spec.tsx +3 -1
  174. package/src/app/agentkit/react/components.spec.tsx +24 -4
  175. package/src/app/agentkit/react/components.tsx +174 -44
  176. package/src/app/agentkit/react/composer-context.spec.tsx +3 -0
  177. package/src/app/agentkit/react/disclosures.spec.tsx +124 -0
  178. package/src/app/agentkit/react/lifecycle.spec.tsx +8 -2
  179. package/src/app/agentkit/react/message-actions.spec.tsx +161 -5
  180. package/src/app/agentkit/react/saved-reply-reload.spec.tsx +206 -0
  181. package/src/app/agentkit/react/styles.css +93 -65
  182. package/src/app/agentkit/react/voice-transcripts.spec.tsx +239 -4
  183. package/src/app/agentkit/react/voice-transcripts.ts +245 -8
  184. package/src/app/chat/AgentKitAssistantChat.spec.tsx +59 -11
  185. package/src/app/chat/AgentKitAssistantChat.tsx +74 -62
  186. package/src/app/chat/agentkit-chat/guided-answers-context.spec.ts +9 -0
  187. package/src/app/chat/agentkit-chat/guided-questions.tsx +17 -7
  188. package/src/app/chat/agentkit-chat/index.ts +1 -0
  189. package/src/app/chat/chat/run-recovery.spec.tsx +45 -0
  190. package/src/app/chat/chat/run-recovery.tsx +6 -1
  191. package/src/app/chat/composer/runtime-adapters.tsx +2 -0
  192. package/src/app/chat/reply-connection-suggestion.spec.ts +121 -0
  193. package/src/app/chat/reply-connection-suggestion.ts +76 -0
  194. package/src/app/i18n/catalogs/ar-SA.ts +10 -0
  195. package/src/app/i18n/catalogs/de-DE.ts +10 -0
  196. package/src/app/i18n/catalogs/en-US.ts +10 -0
  197. package/src/app/i18n/catalogs/es-ES.ts +10 -0
  198. package/src/app/i18n/catalogs/fr-FR.ts +10 -0
  199. package/src/app/i18n/catalogs/hi-IN.ts +10 -0
  200. package/src/app/i18n/catalogs/ja-JP.ts +10 -0
  201. package/src/app/i18n/catalogs/ko-KR.ts +10 -0
  202. package/src/app/i18n/catalogs/pt-BR.ts +10 -0
  203. package/src/app/i18n/catalogs/zh-CN.ts +10 -0
  204. package/src/app/i18n/catalogs/zh-TW.ts +10 -0
  205. package/src/app/mcp-approval/McpApprovalReview.spec.tsx +687 -0
  206. package/src/app/mcp-approval/McpApprovalReview.tsx +913 -0
  207. package/src/app/onboarding/FirstRunOnboarding.spec.tsx +736 -26
  208. package/src/app/onboarding/FirstRunOnboarding.tsx +473 -145
  209. package/src/app/onboarding/GettingStartedCard.tsx +1 -1
  210. package/src/app/onboarding/first-run-builder-credits-flag.spec.ts +13 -0
  211. package/src/app/onboarding/first-run-builder-credits-flag.ts +9 -0
  212. package/src/app/providers/AppProviders.spec.tsx +109 -0
  213. package/src/app/providers/AppProviders.tsx +53 -1
  214. package/src/app/resources/McpConnectionSuggestion.spec.ts +77 -12
  215. package/src/app/resources/McpConnectionSuggestion.tsx +69 -51
  216. package/src/app/settings/VoiceChatSettings.spec.tsx +6 -1
  217. package/src/app/settings/account/PreferencesSettings.spec.tsx +73 -0
  218. package/src/app/settings/account/PreferencesSettings.tsx +24 -7
  219. package/src/app/settings/account/ThemeFamilySettings.tsx +2 -2
  220. package/src/app/settings/account/search-entries.spec.ts +14 -0
  221. package/src/app/settings/account/search-entries.ts +9 -0
  222. package/src/app/settings/shell/SettingsShell.tsx +29 -2
  223. package/src/app/shared/AppShellSkeleton.spec.tsx +2 -2
  224. package/src/app/shared/AppShellSkeleton.tsx +3 -16
  225. package/src/app/shared/EnvironmentBadge.tsx +3 -4
  226. package/src/app/shared/is-builder-io-employee.ts +17 -0
  227. package/src/app/styles/agent-conversation.css +30 -17
  228. package/src/app/styles/theme-families.css +22 -5
  229. package/src/app/styles/theme-families.spec.ts +73 -0
  230. package/src/app/styles/tokens/agent-kit.css +3 -3
  231. package/src/composer/AgentSuggestionBar.overflow.spec.tsx +1 -1
  232. package/src/composer/AttachmentChip.spec.tsx +87 -0
  233. package/src/composer/PromptComposer.context.spec.tsx +8 -4
  234. package/src/composer/PromptComposer.setup-card.spec.tsx +170 -0
  235. package/src/composer/PromptComposer.tsx +125 -14
  236. package/src/composer/RealtimeVoiceMode.spec.tsx +48 -0
  237. package/src/composer/RealtimeVoiceMode.tsx +45 -2
  238. package/src/composer/TiptapComposer.tsx +25 -0
  239. package/src/composer/TiptapComposer.voice-toolbar.spec.tsx +4 -1
  240. package/src/composer/VoiceButton.tsx +8 -3
  241. package/src/composer/index.ts +4 -0
  242. package/src/composer/prompt-attachments.spec.ts +160 -8
  243. package/src/composer/prompt-attachments.ts +221 -12
  244. package/src/composer/realtime-voice-transcript.spec.ts +36 -0
  245. package/src/composer/realtime-voice-transcript.ts +56 -1
  246. package/src/composer/realtime-voice-turn-metrics.spec.ts +175 -0
  247. package/src/composer/realtime-voice-turn-metrics.ts +192 -0
  248. package/src/composer/runtime-adapters.tsx +5 -1
  249. package/src/composer/useRealtimeVoiceMode.session.spec.tsx +487 -2
  250. package/src/composer/useRealtimeVoiceMode.spec.ts +267 -65
  251. package/src/composer/useRealtimeVoiceMode.tsx +716 -139
  252. package/src/composer/voice-chat-icon.tsx +19 -0
  253. package/src/editor/DragHandle.spec.ts +217 -0
  254. package/src/editor/DragHandle.ts +214 -55
  255. package/src/editor/RegistryBlockNode.spec.ts +172 -2
  256. package/src/editor/RegistryBlockNode.tsx +91 -13
  257. package/src/editor/index.ts +4 -0
  258. package/src/styles.css +2 -0
  259. package/src/ui/dropdown-menu.tsx +4 -0
@@ -1152,8 +1152,14 @@ describe("AgentChat lifecycle", () => {
1152
1152
  revision: 2,
1153
1153
  });
1154
1154
  await flush();
1155
- expect(tree.container.querySelector(".agentkit-activities")).toBeNull();
1156
- expect(tree.container.textContent).not.toContain("Worked for");
1155
+ // The saved reply kept no reasoning, so only the turn's time is left, and
1156
+ // with no model or usage recorded it has nothing to open to.
1157
+ const settled = tree.container.querySelectorAll<HTMLElement>(
1158
+ ".agentkit-activities",
1159
+ );
1160
+ expect(settled).toHaveLength(1);
1161
+ expect(settled[0]?.dataset.expandable).toBe("false");
1162
+ expect(settled[0]?.textContent).toBe("Worked for 4s");
1157
1163
  expect(
1158
1164
  tree.container
1159
1165
  .querySelector('[data-message-id="assistant-1"]')
@@ -1,11 +1,15 @@
1
1
  // @vitest-environment happy-dom
2
2
 
3
3
  import { AgentKitClient } from "@agent-native/agentkit/client";
4
- import type {
5
- AgentEvent,
6
- AgentMessage,
7
- AgentToolCall,
8
- AgentTransport,
4
+ import {
5
+ createAgentKitProtocolVersionOffer,
6
+ createCapabilityUnavailableError,
7
+ negotiateAgentKitProtocolVersion,
8
+ parseAgentCapabilitiesDiscovery,
9
+ type AgentEvent,
10
+ type AgentMessage,
11
+ type AgentToolCall,
12
+ type AgentTransport,
9
13
  } from "@agent-native/agentkit/protocol";
10
14
  import { act, type ReactNode } from "react";
11
15
  import { createRoot } from "react-dom/client";
@@ -284,6 +288,111 @@ describe("assistant reply toolbar", () => {
284
288
  expect(labelsOf(set(older, "secondary"))).toEqual(["Fork from here"]);
285
289
  });
286
290
 
291
+ it("names each icon-only action in a tooltip on hover", async () => {
292
+ const { container, toolbar } = await renderChat();
293
+ const latest = toolbar("assistant-latest");
294
+ const user = container.querySelector<HTMLElement>(
295
+ '[data-message-id="user-2"] .agentkit-message-actions',
296
+ )!;
297
+ const hoverName = async (button: HTMLButtonElement) => {
298
+ vi.useFakeTimers();
299
+ try {
300
+ await act(async () => {
301
+ button.dispatchEvent(
302
+ new PointerEvent("pointermove", {
303
+ bubbles: true,
304
+ pointerType: "mouse",
305
+ }),
306
+ );
307
+ vi.advanceTimersByTime(1000);
308
+ });
309
+ const name = document.querySelector('[role="tooltip"]')?.textContent;
310
+ await act(async () => {
311
+ button.dispatchEvent(
312
+ new PointerEvent("pointerleave", {
313
+ bubbles: true,
314
+ pointerType: "mouse",
315
+ }),
316
+ );
317
+ vi.advanceTimersByTime(1000);
318
+ });
319
+ return name;
320
+ } finally {
321
+ vi.useRealTimers();
322
+ }
323
+ };
324
+
325
+ const names: Array<string | null | undefined> = [];
326
+ const hoverAll = async (scope: HTMLElement) => {
327
+ for (const button of Array.from(scope.querySelectorAll("button"))) {
328
+ // The idle set is inert, so only the open set's actions can be hovered.
329
+ if (!button.closest("[inert]")) names.push(await hoverName(button));
330
+ }
331
+ };
332
+ await hoverAll(latest);
333
+ await press(toggle(latest));
334
+ await hoverAll(set(latest, "secondary")!);
335
+ await hoverAll(user);
336
+ expect(names).toEqual([
337
+ "Copy message",
338
+ "Good response",
339
+ "Bad response",
340
+ "More actions",
341
+ "Retry",
342
+ "Fork from here",
343
+ "Copy request ID",
344
+ "Copy message",
345
+ ]);
346
+ });
347
+
348
+ it("shows a degraded action's reason in its tooltip, not a second native title", async () => {
349
+ const reason = "Feedback is queued and may take a minute to record.";
350
+ const { toolbar } = await renderChat({
351
+ transport: {
352
+ async discoverCapabilities() {
353
+ return parseAgentCapabilitiesDiscovery({
354
+ protocol: negotiateAgentKitProtocolVersion(
355
+ createAgentKitProtocolVersionOffer(),
356
+ ),
357
+ discoveredAt: "2026-10-23T00:00:00.000Z",
358
+ capabilities: [
359
+ { id: "threadForking", state: "available" },
360
+ {
361
+ id: "feedback",
362
+ state: "degraded",
363
+ error: createCapabilityUnavailableError("feedback", {
364
+ message: reason,
365
+ retryable: true,
366
+ }),
367
+ },
368
+ ],
369
+ });
370
+ },
371
+ },
372
+ });
373
+ const good = thumb(toolbar("assistant-latest"), "positive");
374
+
375
+ expect(good.disabled).toBe(false);
376
+ expect(good.hasAttribute("title")).toBe(false);
377
+ vi.useFakeTimers();
378
+ try {
379
+ await act(async () => {
380
+ good.dispatchEvent(
381
+ new PointerEvent("pointermove", {
382
+ bubbles: true,
383
+ pointerType: "mouse",
384
+ }),
385
+ );
386
+ vi.advanceTimersByTime(1000);
387
+ });
388
+ } finally {
389
+ vi.useRealTimers();
390
+ }
391
+ expect(document.querySelector('[role="tooltip"]')?.textContent).toBe(
392
+ `Good response${reason}`,
393
+ );
394
+ });
395
+
287
396
  it("shows More as pressed while its actions are open, past its hover fill", async () => {
288
397
  const { toolbar } = await renderChat();
289
398
  const more = toggle(toolbar("assistant-latest"));
@@ -618,6 +727,53 @@ describe("Worked for run details", () => {
618
727
  ).toBe("1");
619
728
  }
620
729
 
730
+ const charged = await renderChat({
731
+ loadRunUsage: async () => ({
732
+ durationMs: null,
733
+ billing: {
734
+ providerCostUsd: 0.02,
735
+ providerCostSource: "reported" as const,
736
+ builderCredits: 0,
737
+ builderCreditsSource: "estimated" as const,
738
+ incomplete: false,
739
+ },
740
+ }),
741
+ events: runEvents("run-latest", [...toolWork], latestAnswer),
742
+ });
743
+ await press(
744
+ charged.container.querySelector(".agentkit-activities-summary"),
745
+ );
746
+ await act(async () => {
747
+ await new Promise((resolve) => setTimeout(resolve, 0));
748
+ });
749
+ const charge = charged.container.querySelector('[data-detail="credits"]');
750
+ expect(charge?.querySelector("dt")?.textContent).toBe("Cost");
751
+ expect(charge?.querySelector("dd")?.textContent).toBe("$0.02");
752
+
753
+ // Auto names no model, so with no steps the zero is the only detail left
754
+ // and still shows.
755
+ const alone = await renderChat({
756
+ followUpModel: "auto",
757
+ loadRunUsage: async () => ({
758
+ durationMs: null,
759
+ billing: {
760
+ providerCostUsd: 0,
761
+ providerCostSource: "reported" as const,
762
+ builderCredits: 0,
763
+ builderCreditsSource: "estimated" as const,
764
+ incomplete: false,
765
+ },
766
+ }),
767
+ events: runEvents("run-latest", [], latestAnswer),
768
+ });
769
+ await press(alone.container.querySelector(".agentkit-activities-summary"));
770
+ await act(async () => {
771
+ await new Promise((resolve) => setTimeout(resolve, 0));
772
+ });
773
+ expect(
774
+ alone.container.querySelector('[data-detail="credits"] dd')?.textContent,
775
+ ).toMatch(/^0\.0\b/);
776
+
621
777
  const unrecorded = await renderChat({
622
778
  loadRunUsage: async () => ({ durationMs: null, billing: null }),
623
779
  events: runEvents("run-latest", [...toolWork], latestAnswer),
@@ -0,0 +1,206 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import { AgentKitClient } from "@agent-native/agentkit/client";
4
+ import { createAgentNativeAgentKitTransport } from "@agent-native/core/client/agentkit-chat/transport";
5
+ import { act } from "react";
6
+ import { createRoot, type Root } from "react-dom/client";
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8
+
9
+ import { AgentKitChat } from "./components.js";
10
+ import { AgentKitProvider } from "./context.js";
11
+
12
+ const threadId = "thread-saved-reply-reload";
13
+ const runId = "run-saved-reply-reload";
14
+ const turnId = "turn-saved-reply-reload";
15
+ const promptId = "message-prompt";
16
+ const streamedId = "message-streamed-reply";
17
+ const startedAt = Date.parse("2026-10-26T21:43:54.411Z");
18
+ const completedAt = startedAt + 19_193;
19
+ const streamedAnswer = "Here are the main answers so you can pick.";
20
+ const savedAnswer = "Here are the main answers, so you can pick your own.";
21
+
22
+ function iso(ms: number): string {
23
+ return new Date(ms).toISOString();
24
+ }
25
+
26
+ function event(sequence: number, occurredAt: number, body: object) {
27
+ return {
28
+ id: `${runId}:${sequence}`,
29
+ threadId,
30
+ runId,
31
+ sequence,
32
+ occurredAt: iso(occurredAt),
33
+ ...body,
34
+ };
35
+ }
36
+
37
+ const activity = {
38
+ id: "activity-read",
39
+ kind: "read",
40
+ label: "Read the brief",
41
+ };
42
+
43
+ // The thread as the server saves it: its own copy of the reply under its own
44
+ // id, beside the chat's copy under the id the run's events use.
45
+ const storedThread = {
46
+ id: threadId,
47
+ threadData: JSON.stringify({
48
+ messages: [
49
+ {
50
+ message: {
51
+ id: `server-user-${runId}`,
52
+ role: "user",
53
+ content: [{ type: "text", text: "Compare them" }],
54
+ metadata: {
55
+ custom: {
56
+ submittedRunId: runId,
57
+ submittedTurnId: turnId,
58
+ agentKitMessageId: promptId,
59
+ },
60
+ },
61
+ },
62
+ parentId: null,
63
+ },
64
+ {
65
+ message: {
66
+ id: `server-${runId}`,
67
+ role: "assistant",
68
+ status: { type: "complete", reason: "stop" },
69
+ content: [{ type: "text", text: savedAnswer }],
70
+ metadata: {
71
+ runId,
72
+ custom: {
73
+ turnId,
74
+ foldedRunIds: [runId],
75
+ agentNativeRunDurationMs: 19_027,
76
+ },
77
+ },
78
+ },
79
+ parentId: `server-user-${runId}`,
80
+ },
81
+ ],
82
+ agentKit: {
83
+ messages: [
84
+ {
85
+ id: promptId,
86
+ role: "user",
87
+ parts: [{ type: "text", text: "Compare them" }],
88
+ },
89
+ {
90
+ id: streamedId,
91
+ role: "assistant",
92
+ status: "complete",
93
+ parts: [{ type: "text", text: streamedAnswer }],
94
+ },
95
+ ],
96
+ events: [
97
+ event(1, startedAt, { type: "run.started" }),
98
+ event(2, startedAt + 200, {
99
+ type: "activity.started",
100
+ activity: { ...activity, status: "running" },
101
+ }),
102
+ event(3, startedAt + 1_200, {
103
+ type: "activity.completed",
104
+ activity: { ...activity, status: "completed" },
105
+ }),
106
+ event(4, completedAt, {
107
+ type: "message.completed",
108
+ message: {
109
+ id: streamedId,
110
+ role: "assistant",
111
+ status: "complete",
112
+ parts: [{ type: "text", text: streamedAnswer }],
113
+ },
114
+ }),
115
+ event(5, completedAt, { type: "run.completed" }),
116
+ ],
117
+ runs: [
118
+ {
119
+ id: runId,
120
+ threadId,
121
+ status: "completed",
122
+ startedAt: iso(startedAt),
123
+ completedAt: iso(completedAt),
124
+ lastSequence: 5,
125
+ activeMessageId: streamedId,
126
+ },
127
+ ],
128
+ activeRunIds: [],
129
+ suggestions: [],
130
+ },
131
+ }),
132
+ runLifecycles: {
133
+ queriedRunIds: [runId],
134
+ runs: [
135
+ {
136
+ id: runId,
137
+ status: "completed",
138
+ startedAt,
139
+ completedAt,
140
+ errorCode: null,
141
+ abortReason: null,
142
+ terminalReason: "done",
143
+ },
144
+ ],
145
+ },
146
+ };
147
+
148
+ function json(value: unknown): Response {
149
+ return new Response(JSON.stringify(value), {
150
+ status: 200,
151
+ headers: { "content-type": "application/json" },
152
+ });
153
+ }
154
+
155
+ let client: AgentKitClient | undefined;
156
+ let container: HTMLDivElement;
157
+ let root: Root;
158
+
159
+ beforeEach(() => {
160
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
161
+ container = document.createElement("div");
162
+ document.body.append(container);
163
+ root = createRoot(container);
164
+ });
165
+
166
+ afterEach(async () => {
167
+ await act(async () => root.unmount());
168
+ client?.dispose();
169
+ client = undefined;
170
+ container.remove();
171
+ vi.unstubAllGlobals();
172
+ });
173
+
174
+ describe("reloading a thread whose saved reply differs from the stream", () => {
175
+ it("shows the saved reply with its Worked for row", async () => {
176
+ const transport = createAgentNativeAgentKitTransport({
177
+ fetch: vi.fn(async (input: string | URL | Request) =>
178
+ String(input).includes("/runs/active")
179
+ ? json({ active: false, status: "completed", runId })
180
+ : json(storedThread),
181
+ ) as typeof fetch,
182
+ });
183
+ client = new AgentKitClient({ transport });
184
+ await client.loadThread(threadId);
185
+ await act(async () =>
186
+ root.render(
187
+ <AgentKitProvider controller={client!} threadId={threadId}>
188
+ <AgentKitChat composer={false} />
189
+ </AgentKitProvider>,
190
+ ),
191
+ );
192
+
193
+ const transcript = container.querySelector(".agentkit-transcript")!;
194
+ const replies = transcript.querySelectorAll(
195
+ '[data-message-id][data-role="assistant"]',
196
+ );
197
+ expect(
198
+ [...replies].map((reply) => reply.getAttribute("data-message-id")),
199
+ ).toEqual([streamedId]);
200
+ expect(replies[0]?.textContent).toContain(savedAnswer);
201
+ expect(transcript.textContent).not.toContain(streamedAnswer);
202
+ expect(
203
+ transcript.querySelector(".agentkit-activities-label")?.textContent,
204
+ ).toBe("Worked for 19s");
205
+ });
206
+ });