@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
@@ -108,6 +108,7 @@ import {
108
108
  ComposerNotice,
109
109
  ComposerNoticeScope,
110
110
  } from "../../../composer/composer-notice.js";
111
+ import { useComposerRuntimeAdapters } from "../../../composer/runtime-adapters.js";
111
112
  import { Dialog, Tooltip } from "../../../design-system/index.js";
112
113
  import { useTableColumnGlide } from "../../../table-column-glide.js";
113
114
  import { Skeleton } from "../../../ui/skeleton.js";
@@ -2263,6 +2264,7 @@ export const AgentActivityGroup = memo(function AgentActivityGroup({
2263
2264
  isCurrentSegment = true,
2264
2265
  messageId,
2265
2266
  foldedReasoningMessageId,
2267
+ summarizesTurn = false,
2266
2268
  }: {
2267
2269
  runId?: RunId;
2268
2270
  afterSequence?: number;
@@ -2273,9 +2275,17 @@ export const AgentActivityGroup = memo(function AgentActivityGroup({
2273
2275
  messageId?: string;
2274
2276
  /** A saved reply whose reasoning before its answer joins this group. */
2275
2277
  foldedReasoningMessageId?: string;
2278
+ /** This segment is its run's whole turn, so it shows a row with no work. */
2279
+ summarizesTurn?: boolean;
2276
2280
  }) {
2277
- const { threadId, slots, registry, labels, formatWorkedForDuration } =
2278
- useAgentKit();
2281
+ const {
2282
+ threadId,
2283
+ slots,
2284
+ registry,
2285
+ labels,
2286
+ formatWorkedForDuration,
2287
+ loadRunUsage,
2288
+ } = useAgentKit();
2279
2289
  const thread = useAgentThreadSlice((previous, next) =>
2280
2290
  sameRunWork(previous, next, runId),
2281
2291
  );
@@ -2554,9 +2564,15 @@ export const AgentActivityGroup = memo(function AgentActivityGroup({
2554
2564
  event.type === "run.failed" ||
2555
2565
  event.type === "run.cancelled"),
2556
2566
  );
2567
+ // A local run, such as voice chat's, never starts on the server, so it
2568
+ // stays queued while it works; its own events show it is working.
2569
+ const localRunWorking =
2570
+ run?.status === "queued" && runEvents.length > 0 && !hasTerminalRunEvent;
2557
2571
  const activelyWorking =
2558
2572
  isCurrentSegment &&
2559
- (run ? run.status === "running" : running && !hasTerminalRunEvent);
2573
+ (run
2574
+ ? run.status === "running" || localRunWorking
2575
+ : running && !hasTerminalRunEvent);
2560
2576
  const waitingLabel =
2561
2577
  isCurrentSegment && run?.status === "awaiting_approval"
2562
2578
  ? labels.waitingForApproval
@@ -2572,12 +2588,48 @@ export const AgentActivityGroup = memo(function AgentActivityGroup({
2572
2588
  (afterSequence === undefined &&
2573
2589
  run !== undefined &&
2574
2590
  ["completed", "failed", "cancelled"].includes(run.status)));
2591
+ const formatDuration = (ms: number) =>
2592
+ formatWorkedForDuration?.(ms) ??
2593
+ formatAgentKitDuration(ms, {
2594
+ hour: labels.durationHourShort,
2595
+ minute: labels.durationMinuteShort,
2596
+ second: labels.durationSecondShort,
2597
+ });
2575
2598
  if (
2576
2599
  activityItems.length === 0 &&
2577
2600
  durableToolResults.length === 0 &&
2578
2601
  !activelyWorking
2579
2602
  ) {
2580
- return null;
2603
+ const runFinished = run
2604
+ ? ["completed", "failed", "cancelled"].includes(run.status)
2605
+ : hasTerminalRunEvent;
2606
+ // Untimed or under a second, a bare "Worked" would read as instant.
2607
+ if (
2608
+ !summarizesTurn ||
2609
+ !runFinished ||
2610
+ durationMs === undefined ||
2611
+ durationMs < 1_000
2612
+ ) {
2613
+ return null;
2614
+ }
2615
+ // With no steps, the run's details (cost, model) are what it opens to, so
2616
+ // it opens only when it can show one of them.
2617
+ const selectedModel = selectedRunModel(thread.messages, messageId);
2618
+ const hasRunDetails =
2619
+ runId !== undefined &&
2620
+ (loadRunUsage !== undefined ||
2621
+ (selectedModel !== undefined && selectedModel !== "auto"));
2622
+ return (
2623
+ <AgentWorkDisclosure
2624
+ label={workedSummaryLabel(durationMs, labels, formatDuration)}
2625
+ icon={<IconClock aria-hidden="true" className="agentkit-icon" />}
2626
+ expandable={hasRunDetails}
2627
+ {...(runId === undefined ? {} : { runId })}
2628
+ {...(messageId === undefined ? {} : { messageId })}
2629
+ >
2630
+ {null}
2631
+ </AgentWorkDisclosure>
2632
+ );
2581
2633
  }
2582
2634
  const currentActivity = activelyWorking
2583
2635
  ? (latestRunningUsefulActivity?.activity ??
@@ -2619,13 +2671,6 @@ export const AgentActivityGroup = memo(function AgentActivityGroup({
2619
2671
  });
2620
2672
  const steps = agentWorkSteps(workItems);
2621
2673
  const hasExpandableActivity = steps.length > 0;
2622
- const formatDuration = (ms: number) =>
2623
- formatWorkedForDuration?.(ms) ??
2624
- formatAgentKitDuration(ms, {
2625
- hour: labels.durationHourShort,
2626
- minute: labels.durationMinuteShort,
2627
- second: labels.durationSecondShort,
2628
- });
2629
2674
  const failedCount = steps.reduce((sum, step) => sum + step.failedCount, 0);
2630
2675
  const reasoningMessageIds = activityItems.flatMap((activity) => {
2631
2676
  const entry = reasoningEntries.get(activity.id);
@@ -3351,7 +3396,8 @@ function AgentWorkDisclosure({
3351
3396
  />
3352
3397
  ) : undefined;
3353
3398
  // The row stays one mounted trigger from "Thinking" through the first
3354
- // activity, so it never flickers; until then it is not a tab stop.
3399
+ // activity, so it never flickers; until then it is not a tab stop. Finished
3400
+ // work with nothing to expand is text, not a disabled button.
3355
3401
  const disclosure = (
3356
3402
  <div
3357
3403
  ref={handoff.inPlaceRef}
@@ -3362,6 +3408,7 @@ function AgentWorkDisclosure({
3362
3408
  data-handoff={handoff.leavingHeight ? "enter" : undefined}
3363
3409
  >
3364
3410
  <AgentKitDisclosureSummary
3411
+ as={expandable || running ? "button" : "static"}
3365
3412
  className="agentkit-activities-summary"
3366
3413
  rowClassName="agentkit-activities-row"
3367
3414
  open={open}
@@ -4826,6 +4873,21 @@ function messageMetadataString(
4826
4873
  return undefined;
4827
4874
  }
4828
4875
 
4876
+ /** The model a reply, or the request before it, was sent with. */
4877
+ function selectedRunModel(
4878
+ messages: readonly AgentMessage[],
4879
+ messageId: string | undefined,
4880
+ ): string | undefined {
4881
+ if (!messageId) return undefined;
4882
+ return (
4883
+ messageMetadataString(
4884
+ agentMessagesById(messages).get(messageId)?.message,
4885
+ "model",
4886
+ ) ??
4887
+ messageMetadataString(findPreviousUserMessage(messages, messageId), "model")
4888
+ );
4889
+ }
4890
+
4829
4891
  /**
4830
4892
  * A finished reply's footer offers a host under its message id, and the
4831
4893
  * reply's work row portals into it so it shares the line with the controls.
@@ -5024,17 +5086,9 @@ function AgentRunDetails({
5024
5086
  toolIds.add(event.activity.id);
5025
5087
  }
5026
5088
  }
5027
- const reply = messageId
5028
- ? agentMessagesById(thread.messages).get(messageId)?.message
5029
- : undefined;
5030
- const request = messageId
5031
- ? findPreviousUserMessage(thread.messages, messageId)
5032
- : undefined;
5033
5089
  return {
5034
5090
  toolCalls: toolIds.size,
5035
- model:
5036
- messageMetadataString(reply, "model") ??
5037
- messageMetadataString(request, "model"),
5091
+ model: selectedRunModel(thread.messages, messageId),
5038
5092
  };
5039
5093
  },
5040
5094
  (previous, next) =>
@@ -5065,10 +5119,11 @@ function AgentRunDetails({
5065
5119
  if (billing.incomplete) {
5066
5120
  return { key: labels.runCreditsUsed, value: labels.runDetailIncomplete };
5067
5121
  }
5068
- // A measured zero has nothing to tell; only a missing or failed reading
5069
- // still shows, so it is never mistaken for a free run.
5070
- if (billing.builderCredits === 0) return null;
5071
- if (billing.builderCredits !== null) {
5122
+ // Zero credits give way to a provider charge, which is the real cost.
5123
+ if (
5124
+ billing.builderCredits !== null &&
5125
+ (billing.builderCredits !== 0 || !billing.providerCostUsd)
5126
+ ) {
5072
5127
  return {
5073
5128
  key: labels.runCreditsUsed,
5074
5129
  value: formatRunUsageAmount(
@@ -5079,9 +5134,9 @@ function AgentRunDetails({
5079
5134
  billing.builderCreditsSource,
5080
5135
  labels,
5081
5136
  ),
5137
+ measuredZero: billing.builderCredits === 0,
5082
5138
  };
5083
5139
  }
5084
- if (billing.providerCostUsd === 0) return null;
5085
5140
  if (billing.providerCostUsd !== null) {
5086
5141
  const usd = billing.providerCostUsd;
5087
5142
  return {
@@ -5098,6 +5153,7 @@ function AgentRunDetails({
5098
5153
  billing.providerCostSource,
5099
5154
  labels,
5100
5155
  ),
5156
+ measuredZero: usd === 0,
5101
5157
  };
5102
5158
  }
5103
5159
  return { key: labels.runCreditsUsed, value: labels.runDetailNotRecorded };
@@ -5110,7 +5166,6 @@ function AgentRunDetails({
5110
5166
  details.model;
5111
5167
  const model = reportedModel === "auto" ? undefined : reportedModel;
5112
5168
  const rows: { id: string; key: string; value: ReactNode }[] = [
5113
- ...(credits ? [{ id: "credits", ...credits }] : []),
5114
5169
  ...(details.toolCalls > 0
5115
5170
  ? [
5116
5171
  {
@@ -5130,6 +5185,12 @@ function AgentRunDetails({
5130
5185
  ]
5131
5186
  : []),
5132
5187
  ];
5188
+ // A measured zero has nothing to tell beside other details; a missing or
5189
+ // failed reading always shows, so it is never mistaken for a free run. Alone,
5190
+ // the zero still shows, so a row that opened to its details is never empty.
5191
+ if (credits && (!credits.measuredZero || rows.length === 0)) {
5192
+ rows.unshift({ id: "credits", key: credits.key, value: credits.value });
5193
+ }
5133
5194
  if (rows.length === 0) return null;
5134
5195
  return (
5135
5196
  <dl
@@ -5221,6 +5282,42 @@ function AgentMessageTimestamp({
5221
5282
  );
5222
5283
  }
5223
5284
 
5285
+ /**
5286
+ * Names an icon-only message action on hover and keyboard focus. Below the
5287
+ * button, so it never covers the message it acts on.
5288
+ */
5289
+ function MessageActionButton({
5290
+ title,
5291
+ ...props
5292
+ }: ComponentPropsWithoutRef<typeof IconButton>) {
5293
+ // A disabled button gets no pointer events, so only a native title can
5294
+ // explain it; an enabled one shows the reason in its tooltip instead, so a
5295
+ // degraded action never opens both.
5296
+ const inactive = Boolean(props.disabled || props.pending);
5297
+ const reason = inactive ? undefined : title;
5298
+ return (
5299
+ <Tooltip
5300
+ content={
5301
+ reason ? (
5302
+ <>
5303
+ {props.label}
5304
+ <span className="block text-muted-foreground">{reason}</span>
5305
+ </>
5306
+ ) : (
5307
+ props.label
5308
+ )
5309
+ }
5310
+ placement="bottom"
5311
+ trigger={
5312
+ <IconButton
5313
+ {...props}
5314
+ {...(inactive && title !== undefined ? { title } : {})}
5315
+ />
5316
+ }
5317
+ />
5318
+ );
5319
+ }
5320
+
5224
5321
  export const AgentMessageActions = memo(function AgentMessageActions({
5225
5322
  value: message,
5226
5323
  threadId,
@@ -5434,7 +5531,7 @@ export const AgentMessageActions = memo(function AgentMessageActions({
5434
5531
  regenerateAction.error;
5435
5532
  const placement = isAssistant ? "inline" : undefined;
5436
5533
  const copyButton = (
5437
- <IconButton
5534
+ <MessageActionButton
5438
5535
  label={copied ? labels.copied : labels.copy}
5439
5536
  icon={
5440
5537
  copied ? (
@@ -5458,7 +5555,7 @@ export const AgentMessageActions = memo(function AgentMessageActions({
5458
5555
  .replace("{{index}}", String(branchNavigation.index))
5459
5556
  .replace("{{count}}", String(branchNavigation.count))}
5460
5557
  >
5461
- <IconButton
5558
+ <MessageActionButton
5462
5559
  label={labels.previousBranch}
5463
5560
  icon={<IconChevronLeft aria-hidden="true" />}
5464
5561
  size="compact"
@@ -5483,7 +5580,7 @@ export const AgentMessageActions = memo(function AgentMessageActions({
5483
5580
  .replace("{{index}}", String(branchNavigation.index))
5484
5581
  .replace("{{count}}", String(branchNavigation.count))}
5485
5582
  </span>
5486
- <IconButton
5583
+ <MessageActionButton
5487
5584
  label={labels.nextBranch}
5488
5585
  icon={<IconChevronRight aria-hidden="true" />}
5489
5586
  size="compact"
@@ -5543,7 +5640,7 @@ export const AgentMessageActions = memo(function AgentMessageActions({
5543
5640
  ? (["positive", "negative"] as const).map((value) => {
5544
5641
  const chosen = feedback === value;
5545
5642
  return (
5546
- <IconButton
5643
+ <MessageActionButton
5547
5644
  key={value}
5548
5645
  label={
5549
5646
  value === "positive"
@@ -5589,7 +5686,7 @@ export const AgentMessageActions = memo(function AgentMessageActions({
5589
5686
  inert={!moreActionsOpen}
5590
5687
  >
5591
5688
  {showRetry ? (
5592
- <IconButton
5689
+ <MessageActionButton
5593
5690
  label={labels.regenerateResponse}
5594
5691
  icon={<IconRefresh aria-hidden="true" />}
5595
5692
  size="compact"
@@ -5601,14 +5698,16 @@ export const AgentMessageActions = memo(function AgentMessageActions({
5601
5698
  position.active ||
5602
5699
  !forkingCapability.enabled
5603
5700
  }
5604
- title={forkingCapability.reason}
5701
+ {...(forkingCapability.reason === undefined
5702
+ ? {}
5703
+ : { title: forkingCapability.reason })}
5605
5704
  onPress={() =>
5606
5705
  void regenerateAction.execute().catch(() => undefined)
5607
5706
  }
5608
5707
  />
5609
5708
  ) : null}
5610
5709
  {showFork ? (
5611
- <IconButton
5710
+ <MessageActionButton
5612
5711
  label={labels.forkFromHere}
5613
5712
  icon={<IconArrowFork aria-hidden="true" />}
5614
5713
  size="compact"
@@ -5623,7 +5722,7 @@ export const AgentMessageActions = memo(function AgentMessageActions({
5623
5722
  />
5624
5723
  ) : null}
5625
5724
  {requestId ? (
5626
- <IconButton
5725
+ <MessageActionButton
5627
5726
  label={requestIdCopied ? labels.copied : labels.copyRequestId}
5628
5727
  icon={
5629
5728
  requestIdCopied ? (
@@ -5645,7 +5744,7 @@ export const AgentMessageActions = memo(function AgentMessageActions({
5645
5744
  ) : null}
5646
5745
  </div>
5647
5746
  {hasSecondaryActions ? (
5648
- <IconButton
5747
+ <MessageActionButton
5649
5748
  elementRef={moreToggleRef}
5650
5749
  label={labels.moreMessageActions}
5651
5750
  icon={<IconDots aria-hidden="true" />}
@@ -5696,13 +5795,15 @@ export const AgentMessageActions = memo(function AgentMessageActions({
5696
5795
  message.metadata?.pendingSubmission !== true &&
5697
5796
  forkingCapability.visible &&
5698
5797
  text.trim() ? (
5699
- <IconButton
5798
+ <MessageActionButton
5700
5799
  label={labels.editMessage}
5701
5800
  icon={<IconPencil aria-hidden="true" />}
5702
5801
  size="compact"
5703
5802
  className="size-6"
5704
5803
  disabled={position.active || !forkingCapability.enabled}
5705
- title={forkingCapability.reason}
5804
+ {...(forkingCapability.reason === undefined
5805
+ ? {}
5806
+ : { title: forkingCapability.reason })}
5706
5807
  aria-pressed={editContext.message?.id === message.id}
5707
5808
  onPress={() => editContext.setMessage(message)}
5708
5809
  />
@@ -6069,6 +6170,7 @@ export interface AgentKitComposerProps extends Omit<
6069
6170
  | "onSlashCommand"
6070
6171
  | "plusMenuMode"
6071
6172
  | "voiceEnabled"
6173
+ | "onVoiceChatStart"
6072
6174
  | "autoFocus"
6073
6175
  | "disabled"
6074
6176
  | "isReferenceTarget"
@@ -6096,6 +6198,7 @@ export interface AgentKitComposerProps extends Omit<
6096
6198
  | "modelStatusChecksEnabled"
6097
6199
  | "showMissingApiKeySetup"
6098
6200
  | "setupCardVisible"
6201
+ | "onModelConnectionRequiredChange"
6099
6202
  | "contextUsageSlot"
6100
6203
  | "attachmentsEnabled"
6101
6204
  | "onAttachmentRequest"
@@ -6167,6 +6270,7 @@ export function AgentKitComposer({
6167
6270
  onSlashCommand,
6168
6271
  plusMenuMode,
6169
6272
  voiceEnabled = false,
6273
+ onVoiceChatStart,
6170
6274
  autoFocus = true,
6171
6275
  toolbarSlot,
6172
6276
  disabled,
@@ -6196,6 +6300,7 @@ export function AgentKitComposer({
6196
6300
  requireAgentEngine = false,
6197
6301
  showMissingApiKeySetup,
6198
6302
  setupCardVisible,
6303
+ onModelConnectionRequiredChange,
6199
6304
  contextUsageSlot,
6200
6305
  attachmentsEnabled,
6201
6306
  onAttachmentRequest,
@@ -6215,7 +6320,10 @@ export function AgentKitComposer({
6215
6320
  onRetryContextItem,
6216
6321
  contextMenuItems,
6217
6322
  attachmentAdapter,
6218
- inlineTextAttachments,
6323
+ // AgentKit always uploads attached files and the server reads their text,
6324
+ // so also pasting a text file into the message sends it twice: the copy
6325
+ // here has only a weak truncation note and a read failure here is silent.
6326
+ inlineTextAttachments = false,
6219
6327
  beforeSend,
6220
6328
  }: AgentKitComposerProps) {
6221
6329
  const {
@@ -6327,13 +6435,14 @@ export function AgentKitComposer({
6327
6435
  };
6328
6436
  }, []);
6329
6437
  const [attachmentError, setAttachmentError] = useState<string | null>(null);
6438
+ const composerTranslate = useComposerRuntimeAdapters().translate;
6330
6439
  useEffect(() => {
6331
6440
  setAttachmentError(null);
6332
6441
  setStopError(null);
6333
6442
  }, [threadId]);
6334
6443
  const composerAttachmentAdapter = useMemo(
6335
- () => attachmentAdapter ?? createChatAttachmentAdapter(),
6336
- [attachmentAdapter],
6444
+ () => attachmentAdapter ?? createChatAttachmentAdapter(composerTranslate),
6445
+ [attachmentAdapter, composerTranslate],
6337
6446
  );
6338
6447
  const starting = useAgentKitSubmissionStarting(threadId);
6339
6448
  const active = hasActiveRuns(thread) || starting;
@@ -6423,7 +6532,7 @@ export function AgentKitComposer({
6423
6532
  <div
6424
6533
  role="group"
6425
6534
  aria-labelledby={editingLabelId}
6426
- className="agentkit-editing-row flex h-6 min-w-0 items-center gap-1.5 text-xs text-muted-foreground"
6535
+ className="agentkit-editing-row flex h-6 min-w-0 items-center gap-2 text-xs text-muted-foreground"
6427
6536
  data-agentkit-editing-row="true"
6428
6537
  >
6429
6538
  <Tooltip
@@ -6432,7 +6541,7 @@ export function AgentKitComposer({
6432
6541
  trigger={
6433
6542
  <span
6434
6543
  tabIndex={0}
6435
- className="inline-flex min-w-0 items-center gap-1.5 rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
6544
+ className="inline-flex min-w-0 items-center gap-2 rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
6436
6545
  >
6437
6546
  <IconPencil aria-hidden="true" className="size-4 shrink-0" />
6438
6547
  <span id={editingLabelId} className="truncate">
@@ -6910,6 +7019,7 @@ export function AgentKitComposer({
6910
7019
  requireAgentEngine,
6911
7020
  showMissingApiKeySetup,
6912
7021
  setupCardVisible,
7022
+ onModelConnectionRequiredChange,
6913
7023
  contextUsageSlot,
6914
7024
  layoutVariant,
6915
7025
  toolbarSlot,
@@ -6923,6 +7033,7 @@ export function AgentKitComposer({
6923
7033
  slashSkills,
6924
7034
  onSlashCommand,
6925
7035
  voiceEnabled,
7036
+ onVoiceChatStart,
6926
7037
  replyPlaceholder: placeholder ?? labels.composerReplyPlaceholder,
6927
7038
  onEscape: editingMessage ? cancelEditing : undefined,
6928
7039
  initialText: composerInitialText,
@@ -8295,6 +8406,7 @@ export function AgentKitChat({
8295
8406
  throughSequence,
8296
8407
  messageId,
8297
8408
  foldedReasoningMessageId,
8409
+ summarizesTurn,
8298
8410
  }: {
8299
8411
  runId: RunId;
8300
8412
  anchor: string;
@@ -8302,6 +8414,7 @@ export function AgentKitChat({
8302
8414
  throughSequence?: number | undefined;
8303
8415
  messageId?: string | undefined;
8304
8416
  foldedReasoningMessageId?: string | undefined;
8417
+ summarizesTurn?: boolean | undefined;
8305
8418
  }) => {
8306
8419
  const key = `run-work:${threadId}:${runId}:${anchor}`;
8307
8420
  const resetKey = `${anchor}:${runRevision(runId)}`;
@@ -8313,6 +8426,7 @@ export function AgentKitChat({
8313
8426
  throughSequence,
8314
8427
  messageId,
8315
8428
  foldedReasoningMessageId,
8429
+ summarizesTurn,
8316
8430
  ],
8317
8431
  () =>
8318
8432
  renderRunWorkElement({
@@ -8323,6 +8437,7 @@ export function AgentKitChat({
8323
8437
  throughSequence,
8324
8438
  messageId,
8325
8439
  foldedReasoningMessageId,
8440
+ summarizesTurn,
8326
8441
  }),
8327
8442
  );
8328
8443
  };
@@ -8334,6 +8449,7 @@ export function AgentKitChat({
8334
8449
  throughSequence,
8335
8450
  messageId,
8336
8451
  foldedReasoningMessageId,
8452
+ summarizesTurn,
8337
8453
  }: {
8338
8454
  key: string;
8339
8455
  resetKey: string;
@@ -8342,6 +8458,7 @@ export function AgentKitChat({
8342
8458
  throughSequence?: number | undefined;
8343
8459
  messageId?: string | undefined;
8344
8460
  foldedReasoningMessageId?: string | undefined;
8461
+ summarizesTurn?: boolean | undefined;
8345
8462
  }) => (
8346
8463
  <AgentKitSurfaceBoundary key={key} surface="activity" resetKey={resetKey}>
8347
8464
  <AgentCollaborationFeed
@@ -8362,6 +8479,7 @@ export function AgentKitChat({
8362
8479
  isCurrentSegment={throughSequence === undefined}
8363
8480
  messageId={messageId}
8364
8481
  foldedReasoningMessageId={foldedReasoningMessageId}
8482
+ summarizesTurn={summarizesTurn}
8365
8483
  />
8366
8484
  </AgentKitSurfaceBoundary>
8367
8485
  );
@@ -8535,10 +8653,21 @@ export function AgentKitChat({
8535
8653
  isAssistantBoundary && runId
8536
8654
  ? previousSegmentEndByRun.get(runId)
8537
8655
  : undefined;
8656
+ // A turn that only answered still gets one "Worked for" row: it is the
8657
+ // run's only reply, nothing ran after it, and it shows no reasoning row.
8658
+ const summarizesTurn = Boolean(
8659
+ isAssistantBoundary &&
8660
+ runId &&
8661
+ previousAssistant === undefined &&
8662
+ lastAssistantMessagesByRun.get(runId)?.id === message.id &&
8663
+ !hasRunWork(runId, sequence) &&
8664
+ !hasVisibleReasoning(message),
8665
+ );
8538
8666
  const rendersRunWork = Boolean(
8539
8667
  isAssistantBoundary &&
8540
8668
  runId &&
8541
- hasRunWork(runId, previousAssistant?.sequence, sequence),
8669
+ (summarizesTurn ||
8670
+ hasRunWork(runId, previousAssistant?.sequence, sequence)),
8542
8671
  );
8543
8672
  // A saved reply's reasoning before its answer joins the work group above
8544
8673
  // it, so the turn shows one disclosure; the stream did not place it there.
@@ -8556,6 +8685,7 @@ export function AgentKitChat({
8556
8685
  throughSequence: sequence,
8557
8686
  messageId: message.id,
8558
8687
  foldedReasoningMessageId: foldsReasoning ? message.id : undefined,
8688
+ summarizesTurn,
8559
8689
  }),
8560
8690
  );
8561
8691
  }
@@ -247,6 +247,9 @@ describe("AgentKit composer context submission", () => {
247
247
  ),
248
248
  );
249
249
  expect(capture.props?.contextMenuItems).toBe(contextMenuItems);
250
+ // Files are uploaded and read by the server; inlining them too would send
251
+ // each text file twice.
252
+ expect(capture.props?.inlineTextAttachments).toBe(false);
250
253
  expect(
251
254
  capture.props?.contextMenuItems?.[0].picker?.onSelect,
252
255
  ).toBeUndefined();
@@ -904,3 +904,127 @@ describe("work before a message sent into the running turn", () => {
904
904
  ).toBeTruthy();
905
905
  });
906
906
  });
907
+
908
+ describe("a turn that only answered", () => {
909
+ const reply: AgentMessage = {
910
+ ...answer([{ type: "text", text: "Answer." }]),
911
+ metadata: { model: "claude-sonnet-5-5" },
912
+ };
913
+ // Production runs report this before the model answers; it is not work.
914
+ const startingAgent = {
915
+ type: "activity.started" as const,
916
+ occurredAt: at(100),
917
+ activity: {
918
+ id: `agentkit:internal:${RUN}:starting-agent`,
919
+ kind: "model",
920
+ label: "Starting agent",
921
+ status: "running" as const,
922
+ },
923
+ };
924
+
925
+ function answeredAt(
926
+ ms: number,
927
+ work: Array<Omit<AgentEvent, "id" | "threadId" | "runId" | "sequence">>,
928
+ finished = true,
929
+ ): AgentEvent[] {
930
+ return [
931
+ { type: "run.started", occurredAt: at(0) },
932
+ ...work,
933
+ { type: "message.completed", occurredAt: at(ms), message: reply },
934
+ ...(finished
935
+ ? [{ type: "run.completed", occurredAt: at(ms + 500) }]
936
+ : []),
937
+ ].map(
938
+ (event, index) =>
939
+ ({
940
+ ...event,
941
+ id: `event-${index}`,
942
+ threadId: THREAD,
943
+ runId: RUN,
944
+ sequence: index + 1,
945
+ }) as AgentEvent,
946
+ );
947
+ }
948
+
949
+ it("shows a Worked for row under its reply that opens to the run details", async () => {
950
+ for (const work of [[startingAgent], []]) {
951
+ const container = await renderThread(
952
+ [user, reply],
953
+ answeredAt(4_000, work),
954
+ );
955
+ const rows = container.querySelectorAll<HTMLElement>(
956
+ ".agentkit-activities",
957
+ );
958
+ expect(rows).toHaveLength(1);
959
+ const row = rows[0]!;
960
+ expect(
961
+ row.querySelector(".agentkit-activities-summary")?.textContent,
962
+ ).toBe("Worked for 4s");
963
+ expect(row.closest(".agentkit-message-actions")).not.toBeNull();
964
+ const trigger = row.querySelector<HTMLElement>(
965
+ ".agentkit-disclosure-summary",
966
+ );
967
+ expectSharedSummary(trigger);
968
+ await click(trigger);
969
+ expect(row.dataset.open).toBe("true");
970
+ expect(row.querySelector(".agentkit-run-details")?.textContent).toContain(
971
+ "claude-sonnet-5-5",
972
+ );
973
+ expect(row.querySelector(".agentkit-work-steps")).toBeNull();
974
+ }
975
+ });
976
+
977
+ it("shows text when the run has no details to open to", async () => {
978
+ const unnamed: AgentMessage = { ...reply, metadata: {} };
979
+ const container = await renderThread(
980
+ [user, unnamed],
981
+ answeredAt(4_000, [startingAgent]).map((event) =>
982
+ event.type === "message.completed"
983
+ ? { ...event, message: unnamed }
984
+ : event,
985
+ ),
986
+ );
987
+ const row = container.querySelector<HTMLElement>(".agentkit-activities");
988
+ expect(row?.dataset.expandable).toBe("false");
989
+ expect(
990
+ row?.querySelector(".agentkit-activities-summary")?.textContent,
991
+ ).toBe("Worked for 4s");
992
+ expect(row?.querySelector("button")).toBeNull();
993
+ });
994
+
995
+ it("shows no row under a second, untimed, or still running", async () => {
996
+ const instant = await renderThread(
997
+ [user, reply],
998
+ answeredAt(600, [startingAgent]),
999
+ );
1000
+ expect(instant.querySelector(".agentkit-activities")).toBeNull();
1001
+
1002
+ const streaming = await renderThread(
1003
+ [user, { ...reply, status: "streaming" }],
1004
+ answeredAt(4_000, [startingAgent], false),
1005
+ );
1006
+ expect(streaming.querySelector(".agentkit-activities")).toBeNull();
1007
+
1008
+ // A reloaded thread has no run timing.
1009
+ const reloaded = await renderThread([user, reply], []);
1010
+ expect(reloaded.querySelector(".agentkit-activities")).toBeNull();
1011
+ });
1012
+
1013
+ it("keeps one expandable row for a turn with activity", async () => {
1014
+ const container = await renderThread(
1015
+ [user, reply],
1016
+ answeredAt(4_000, [startingAgent, ...activityEvents(1)]),
1017
+ );
1018
+ const rows = container.querySelectorAll<HTMLElement>(
1019
+ ".agentkit-activities",
1020
+ );
1021
+ expect(rows).toHaveLength(1);
1022
+ expect(rows[0]!.dataset.expandable).toBe("true");
1023
+ expectSharedSummary(
1024
+ rows[0]!.querySelector<HTMLElement>(".agentkit-disclosure-summary"),
1025
+ );
1026
+ expect(
1027
+ rows[0]!.querySelector(".agentkit-activities-summary")?.textContent,
1028
+ ).toBe("Worked for 4s");
1029
+ });
1030
+ });