@agent-native/toolkit 0.213.1 → 0.216.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 (222) hide show
  1. package/agent-native.eject.json +9 -0
  2. package/dist/app/agentkit/react/chat.d.ts.map +1 -1
  3. package/dist/app/agentkit/react/chat.js +2 -2
  4. package/dist/app/agentkit/react/chat.js.map +1 -1
  5. package/dist/app/agentkit/react/components.d.ts +4 -1
  6. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  7. package/dist/app/agentkit/react/components.js +73 -13
  8. package/dist/app/agentkit/react/components.js.map +1 -1
  9. package/dist/app/agentkit/react/context.d.ts +19 -1
  10. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  11. package/dist/app/agentkit/react/context.js +7 -1
  12. package/dist/app/agentkit/react/context.js.map +1 -1
  13. package/dist/app/agentkit/react/index.d.ts +2 -0
  14. package/dist/app/agentkit/react/index.d.ts.map +1 -1
  15. package/dist/app/agentkit/react/index.js +2 -0
  16. package/dist/app/agentkit/react/index.js.map +1 -1
  17. package/dist/app/agentkit/react/message-content-order.d.ts +21 -0
  18. package/dist/app/agentkit/react/message-content-order.d.ts.map +1 -0
  19. package/dist/app/agentkit/react/message-content-order.js +173 -0
  20. package/dist/app/agentkit/react/message-content-order.js.map +1 -0
  21. package/dist/app/agentkit/react/root.d.ts +6 -1
  22. package/dist/app/agentkit/react/root.d.ts.map +1 -1
  23. package/dist/app/agentkit/react/root.js +2 -2
  24. package/dist/app/agentkit/react/root.js.map +1 -1
  25. package/dist/app/agentkit/react/styles.css +45 -8
  26. package/dist/app/agentkit/react/transcript-layout.d.ts +51 -0
  27. package/dist/app/agentkit/react/transcript-layout.d.ts.map +1 -0
  28. package/dist/app/agentkit/react/transcript-layout.js +34 -0
  29. package/dist/app/agentkit/react/transcript-layout.js.map +1 -0
  30. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  31. package/dist/app/chat/AgentKitAssistantChat.js +9 -4
  32. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  33. package/dist/app/chat/AgentPanel.d.ts +16 -2
  34. package/dist/app/chat/AgentPanel.d.ts.map +1 -1
  35. package/dist/app/chat/AgentPanel.js +26 -5
  36. package/dist/app/chat/AgentPanel.js.map +1 -1
  37. package/dist/app/chat/chat/index.d.ts +2 -0
  38. package/dist/app/chat/chat/index.d.ts.map +1 -1
  39. package/dist/app/chat/chat/index.js.map +1 -1
  40. package/dist/app/chat/chat/surface-types.d.ts +15 -0
  41. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  42. package/dist/app/chat/chat/surface-types.js.map +1 -1
  43. package/dist/app/chat/chat/tool-call-display.d.ts +7 -2
  44. package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
  45. package/dist/app/chat/chat/tool-call-display.js +10 -9
  46. package/dist/app/chat/chat/tool-call-display.js.map +1 -1
  47. package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts.map +1 -1
  48. package/dist/app/chat/chat/widgets/DataChartRenderer.js +300 -33
  49. package/dist/app/chat/chat/widgets/DataChartRenderer.js.map +1 -1
  50. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts +3 -2
  51. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts.map +1 -1
  52. package/dist/app/chat/chat/widgets/DataChartWidget.js +18 -9
  53. package/dist/app/chat/chat/widgets/DataChartWidget.js.map +1 -1
  54. package/dist/app/chat/chat/widgets/DataInsightsWidget.js +1 -1
  55. package/dist/app/chat/chat/widgets/DataInsightsWidget.js.map +1 -1
  56. package/dist/app/chat/chat/widgets/DataTableWidget.d.ts +3 -2
  57. package/dist/app/chat/chat/widgets/DataTableWidget.d.ts.map +1 -1
  58. package/dist/app/chat/chat/widgets/DataTableWidget.js +10 -66
  59. package/dist/app/chat/chat/widgets/DataTableWidget.js.map +1 -1
  60. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts +11 -0
  61. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts.map +1 -0
  62. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js +133 -0
  63. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js.map +1 -0
  64. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js +2 -2
  65. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js.map +1 -1
  66. package/dist/app/chat/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  67. package/dist/app/chat/mcp-apps/McpAppRenderer.js +6 -5
  68. package/dist/app/chat/mcp-apps/McpAppRenderer.js.map +1 -1
  69. package/dist/app/chat/mcp-apps/mcp-app-theme.d.ts +2 -0
  70. package/dist/app/chat/mcp-apps/mcp-app-theme.d.ts.map +1 -0
  71. package/dist/app/chat/mcp-apps/mcp-app-theme.js +11 -0
  72. package/dist/app/chat/mcp-apps/mcp-app-theme.js.map +1 -0
  73. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  74. package/dist/app/i18n/catalogs/ar-SA.js +9 -0
  75. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  76. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  77. package/dist/app/i18n/catalogs/de-DE.js +9 -0
  78. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  79. package/dist/app/i18n/catalogs/en-US.d.ts +9 -0
  80. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  81. package/dist/app/i18n/catalogs/en-US.js +9 -0
  82. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  83. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  84. package/dist/app/i18n/catalogs/es-ES.js +9 -0
  85. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  86. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  87. package/dist/app/i18n/catalogs/fr-FR.js +9 -0
  88. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  89. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  90. package/dist/app/i18n/catalogs/hi-IN.js +9 -0
  91. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  92. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  93. package/dist/app/i18n/catalogs/ja-JP.js +9 -0
  94. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  95. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  96. package/dist/app/i18n/catalogs/ko-KR.js +9 -0
  97. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  98. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  99. package/dist/app/i18n/catalogs/pt-BR.js +9 -0
  100. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  101. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  102. package/dist/app/i18n/catalogs/zh-CN.js +9 -0
  103. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  104. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  105. package/dist/app/i18n/catalogs/zh-TW.js +9 -0
  106. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  107. package/dist/app/providers/AppProviders.d.ts +2 -1
  108. package/dist/app/providers/AppProviders.d.ts.map +1 -1
  109. package/dist/app/providers/AppProviders.js +4 -3
  110. package/dist/app/providers/AppProviders.js.map +1 -1
  111. package/dist/app/providers/ThemeFamilySync.d.ts +2 -0
  112. package/dist/app/providers/ThemeFamilySync.d.ts.map +1 -0
  113. package/dist/app/providers/ThemeFamilySync.js +6 -0
  114. package/dist/app/providers/ThemeFamilySync.js.map +1 -0
  115. package/dist/app/settings/account/PreferencesSettings.d.ts.map +1 -1
  116. package/dist/app/settings/account/PreferencesSettings.js +2 -1
  117. package/dist/app/settings/account/PreferencesSettings.js.map +1 -1
  118. package/dist/app/settings/account/ThemeFamilySettings.d.ts +2 -0
  119. package/dist/app/settings/account/ThemeFamilySettings.d.ts.map +1 -0
  120. package/dist/app/settings/account/ThemeFamilySettings.js +31 -0
  121. package/dist/app/settings/account/ThemeFamilySettings.js.map +1 -0
  122. package/dist/app/settings/account/search-entries.d.ts.map +1 -1
  123. package/dist/app/settings/account/search-entries.js +6 -0
  124. package/dist/app/settings/account/search-entries.js.map +1 -1
  125. package/dist/app/shared/DefaultSpinner.d.ts.map +1 -1
  126. package/dist/app/shared/DefaultSpinner.js +4 -5
  127. package/dist/app/shared/DefaultSpinner.js.map +1 -1
  128. package/dist/app/shared/ErrorBoundary.d.ts +1 -0
  129. package/dist/app/shared/ErrorBoundary.d.ts.map +1 -1
  130. package/dist/app/shared/ErrorBoundary.js +27 -12
  131. package/dist/app/shared/ErrorBoundary.js.map +1 -1
  132. package/dist/app/shared/PoweredByBadge.d.ts.map +1 -1
  133. package/dist/app/shared/PoweredByBadge.js +8 -49
  134. package/dist/app/shared/PoweredByBadge.js.map +1 -1
  135. package/dist/app/styles/agent-conversation.css +0 -23
  136. package/dist/app/styles/agent-native.css +1 -1
  137. package/dist/app/styles/theme-families.css +66 -0
  138. package/dist/composer/ComposerContextBanner.d.ts +24 -0
  139. package/dist/composer/ComposerContextBanner.d.ts.map +1 -0
  140. package/dist/composer/ComposerContextBanner.js +9 -0
  141. package/dist/composer/ComposerContextBanner.js.map +1 -0
  142. package/dist/composer/ComposerModePicker.d.ts +26 -0
  143. package/dist/composer/ComposerModePicker.d.ts.map +1 -0
  144. package/dist/composer/ComposerModePicker.js +14 -0
  145. package/dist/composer/ComposerModePicker.js.map +1 -0
  146. package/dist/composer/index.d.ts +2 -0
  147. package/dist/composer/index.d.ts.map +1 -1
  148. package/dist/composer/index.js +2 -0
  149. package/dist/composer/index.js.map +1 -1
  150. package/dist/styles.css +1 -0
  151. package/package.json +3 -3
  152. package/src/app/agentkit/react/chat.tsx +2 -0
  153. package/src/app/agentkit/react/components.interaction.spec.tsx +107 -0
  154. package/src/app/agentkit/react/components.spec.tsx +40 -1
  155. package/src/app/agentkit/react/components.tsx +186 -31
  156. package/src/app/agentkit/react/context.tsx +29 -0
  157. package/src/app/agentkit/react/index.ts +2 -0
  158. package/src/app/agentkit/react/lifecycle.spec.tsx +289 -4
  159. package/src/app/agentkit/react/message-content-order.spec.ts +190 -0
  160. package/src/app/agentkit/react/message-content-order.ts +246 -0
  161. package/src/app/agentkit/react/root.tsx +11 -0
  162. package/src/app/agentkit/react/styles.css +45 -8
  163. package/src/app/agentkit/react/transcript-layout.spec.ts +72 -0
  164. package/src/app/agentkit/react/transcript-layout.ts +97 -0
  165. package/src/app/auth/AuthPage.attribution.spec.tsx +6 -0
  166. package/src/app/chat/AgentKitAssistantChat.spec.tsx +58 -0
  167. package/src/app/chat/AgentKitAssistantChat.tsx +15 -1
  168. package/src/app/chat/AgentPanel.header.spec.ts +72 -14
  169. package/src/app/chat/AgentPanel.page-header-share.spec.tsx +101 -0
  170. package/src/app/chat/AgentPanel.tsx +111 -55
  171. package/src/app/chat/chat/index.ts +2 -0
  172. package/src/app/chat/chat/surface-types.ts +18 -0
  173. package/src/app/chat/chat/tool-call-display.spec.tsx +83 -0
  174. package/src/app/chat/chat/tool-call-display.tsx +40 -8
  175. package/src/app/chat/chat/widgets/DataChartRenderer.spec.tsx +849 -0
  176. package/src/app/chat/chat/widgets/DataChartRenderer.tsx +567 -72
  177. package/src/app/chat/chat/widgets/DataChartWidget.spec.tsx +432 -0
  178. package/src/app/chat/chat/widgets/DataChartWidget.ssr.spec.tsx +25 -0
  179. package/src/app/chat/chat/widgets/DataChartWidget.tsx +58 -33
  180. package/src/app/chat/chat/widgets/DataInsightsWidget.tsx +2 -2
  181. package/src/app/chat/chat/widgets/DataTableWidget.spec.tsx +37 -3
  182. package/src/app/chat/chat/widgets/DataTableWidget.tsx +22 -86
  183. package/src/app/chat/chat/widgets/DataWidgetDisplayControls.tsx +321 -0
  184. package/src/app/chat/chat/widgets/builtin-tool-renderers.tsx +5 -5
  185. package/src/app/chat/mcp-apps/McpAppRenderer.tsx +10 -5
  186. package/src/app/chat/mcp-apps/mcp-app-theme.spec.ts +30 -0
  187. package/src/app/chat/mcp-apps/mcp-app-theme.ts +11 -0
  188. package/src/app/i18n/catalogs/ar-SA.ts +10 -0
  189. package/src/app/i18n/catalogs/de-DE.ts +11 -0
  190. package/src/app/i18n/catalogs/en-US.ts +11 -0
  191. package/src/app/i18n/catalogs/es-ES.ts +12 -0
  192. package/src/app/i18n/catalogs/fr-FR.ts +12 -0
  193. package/src/app/i18n/catalogs/hi-IN.ts +10 -0
  194. package/src/app/i18n/catalogs/ja-JP.ts +11 -0
  195. package/src/app/i18n/catalogs/ko-KR.ts +10 -0
  196. package/src/app/i18n/catalogs/pt-BR.ts +11 -0
  197. package/src/app/i18n/catalogs/zh-CN.ts +9 -0
  198. package/src/app/i18n/catalogs/zh-TW.ts +10 -0
  199. package/src/app/providers/AppProviders.spec.tsx +71 -0
  200. package/src/app/providers/AppProviders.tsx +10 -0
  201. package/src/app/providers/ThemeFamilySync.tsx +6 -0
  202. package/src/app/settings/account/PreferencesSettings.spec.tsx +13 -2
  203. package/src/app/settings/account/PreferencesSettings.tsx +8 -0
  204. package/src/app/settings/account/ThemeFamilySettings.tsx +61 -0
  205. package/src/app/settings/account/search-entries.ts +6 -0
  206. package/src/app/shared/DefaultSpinner.spec.tsx +14 -0
  207. package/src/app/shared/DefaultSpinner.tsx +4 -5
  208. package/src/app/shared/ErrorBoundary.spec.tsx +45 -2
  209. package/src/app/shared/ErrorBoundary.tsx +37 -12
  210. package/src/app/shared/PoweredByBadge.spec.tsx +16 -0
  211. package/src/app/shared/PoweredByBadge.tsx +8 -50
  212. package/src/app/styles/agent-conversation.css +0 -23
  213. package/src/app/styles/agent-native.css +1 -1
  214. package/src/app/styles/agent-native.spec.ts +18 -0
  215. package/src/app/styles/theme-families.css +66 -0
  216. package/src/app/styles/theme-families.spec.ts +29 -0
  217. package/src/composer/ComposerContextBanner.spec.tsx +100 -0
  218. package/src/composer/ComposerContextBanner.tsx +77 -0
  219. package/src/composer/ComposerModePicker.spec.tsx +101 -0
  220. package/src/composer/ComposerModePicker.tsx +98 -0
  221. package/src/composer/index.ts +9 -0
  222. package/src/styles.css +1 -0
@@ -2093,12 +2093,22 @@ export const AgentKitAssistantChat = forwardRef<
2093
2093
  threadId={threadId}
2094
2094
  load={agentKitLoad}
2095
2095
  onLoadError={onThreadRestoreLoadError}
2096
- slots={agentKitSlots}
2096
+ slots={
2097
+ props.workedForSummarySlot
2098
+ ? {
2099
+ ...agentKitSlots,
2100
+ workedForSummary: props.workedForSummarySlot,
2101
+ }
2102
+ : agentKitSlots
2103
+ }
2097
2104
  registry={agentKitRegistry}
2098
2105
  labels={labels}
2106
+ resolveMessageContentOrder={props.resolveMessageContentOrder}
2099
2107
  branchNavigation={props.branchNavigation}
2100
2108
  loadRunUsage={loadRunUsage}
2101
2109
  buildFeedbackReport={formatFeedbackReport}
2110
+ workedForRowLayout={props.workedForRowLayout}
2111
+ formatWorkedForDuration={props.formatWorkedForDuration}
2102
2112
  onThreadForked={(thread) => props.onForkedThread?.(thread.id)}
2103
2113
  onCopyMessage={({ text }) => {
2104
2114
  const html = renderMarkdownToClipboardHtml(text);
@@ -5046,6 +5056,7 @@ const AgentKitAssistantChatBody = forwardRef<
5046
5056
  className={props.className}
5047
5057
  composerProps={{ attachmentsEnabled: fileStorageConfigured }}
5048
5058
  hasRenderedMessages={hasRenderedMessages}
5059
+ transcriptLayout={props.transcriptLayout}
5049
5060
  emptyComposerPlacement={
5050
5061
  props.centerComposerWhenEmpty && !isRestoring ? "center" : "bottom"
5051
5062
  }
@@ -5979,6 +5990,7 @@ function AgentKitComposerSurface({
5979
5990
  })}
5980
5991
  />
5981
5992
  {props.composerSlot}
5993
+ {props.composerContextBannerSlot}
5982
5994
  {showHomeIntro ? (
5983
5995
  <div className="agentkit-home-intro">{props.homeIntroSlot}</div>
5984
5996
  ) : null}
@@ -6461,6 +6473,8 @@ function AgentKitTool({ value, active }: AgentKitRenderProps<AgentToolCall>) {
6461
6473
  chatUI={asRecord(metadata.chatUI) as never}
6462
6474
  activity={metadata.activity === true}
6463
6475
  isActiveTail={active === true}
6476
+ workedForRowLayout={surface.props.workedForRowLayout}
6477
+ formatWorkedForDuration={surface.props.formatWorkedForDuration}
6464
6478
  />
6465
6479
  </SuppressInlineOpenAppContext.Provider>
6466
6480
  </ChatRunningContext.Provider>
@@ -31,6 +31,8 @@ import {
31
31
  shouldHandleAgentSidebarToggle,
32
32
  shouldHandleAgentPanelChatShortcut,
33
33
  shouldShowAgentPanelFullViewAction,
34
+ resolveAgentPanelPageHeaderActions,
35
+ resolveAgentPanelPageToolbarSlot,
34
36
  shouldShowAgentPanelPageHeader,
35
37
  shouldShowAgentPanelPageNewChatButton,
36
38
  shouldShowAgentPanelChatTabBar,
@@ -350,22 +352,60 @@ describe("AgentPanel header tab visibility", () => {
350
352
  expect(shouldDefaultAgentChatSurfacePageHeader(undefined)).toBe(false);
351
353
  });
352
354
 
353
- it("exposes page header composition without moving it into app chrome", () => {
354
- const source = readSource("src/app/chat/AgentPanel.tsx", "utf8");
355
+ it("keeps share and host toolbar actions when the title menu is hidden", async () => {
356
+ expect(
357
+ resolveAgentPanelPageHeaderActions({
358
+ showPageTitleMenu: false,
359
+ canShareActiveTab: true,
360
+ }),
361
+ ).toEqual({ showPageTitleMenu: false, showShareAction: true });
355
362
 
356
- expect(source).toContain('data-agent-page-chat-header=""');
357
- expect(source).toContain("pageHeaderLeadingSlot");
358
- expect(source).toContain("pageToolbarSlot");
359
- expect(source).toContain("activeTab?.label");
360
- expect(source).toContain('data-agent-page-title-menu=""');
361
- expect(source).toContain("<IconShare3 size={15}");
362
- expect(source.indexOf("<IconShare3 size={15}")).toBeLessThan(
363
- source.indexOf(
364
- "{pageToolbarSlot}",
365
- source.indexOf("triggerContent={<IconShare3"),
366
- ),
363
+ const container = document.createElement("div");
364
+ document.body.append(container);
365
+ const root = createRoot(container);
366
+ const toggleHistory = vi.fn();
367
+ const clearActiveTab = vi.fn();
368
+ const toolbar = resolveAgentPanelPageToolbarSlot(
369
+ (actions) =>
370
+ React.createElement(
371
+ "div",
372
+ null,
373
+ React.createElement(
374
+ "button",
375
+ { onClick: actions.toggleHistory },
376
+ actions.showHistory ? "Hide chats" : "All chats",
377
+ ),
378
+ actions.activeTabMessageCount > 0
379
+ ? React.createElement(
380
+ "button",
381
+ { onClick: actions.clearActiveTab },
382
+ "Clear chat",
383
+ )
384
+ : null,
385
+ ),
386
+ {
387
+ activeTabMessageCount: 2,
388
+ clearActiveTab,
389
+ showHistory: false,
390
+ toggleHistory,
391
+ },
367
392
  );
368
- expect(source).toContain("border-b border-border/70");
393
+
394
+ try {
395
+ await act(async () => root.render(toolbar));
396
+ const buttons = container.querySelectorAll("button");
397
+ expect([...buttons].map((button) => button.textContent)).toEqual([
398
+ "All chats",
399
+ "Clear chat",
400
+ ]);
401
+ await act(async () => buttons[0]?.click());
402
+ await act(async () => buttons[1]?.click());
403
+ expect(toggleHistory).toHaveBeenCalledOnce();
404
+ expect(clearActiveTab).toHaveBeenCalledOnce();
405
+ } finally {
406
+ await act(async () => root.unmount());
407
+ container.remove();
408
+ }
369
409
  });
370
410
 
371
411
  it("normalizes legacy and unknown modes back to chat", () => {
@@ -1193,6 +1233,24 @@ describe("AgentChatSurface chrome defaults", () => {
1193
1233
  expect(surface.props.showHeader).toBe(true);
1194
1234
  expect(surface.props.showTabBar).toBe(true);
1195
1235
  });
1236
+
1237
+ it("allows a page host to hide only the title menu", () => {
1238
+ const surface = AgentChatSurface({
1239
+ mode: "page",
1240
+ showPageTitleMenu: false,
1241
+ });
1242
+ const panel = surface.props.children[1];
1243
+ const headerActions = resolveAgentPanelPageHeaderActions({
1244
+ showPageTitleMenu: false,
1245
+ canShareActiveTab: true,
1246
+ });
1247
+
1248
+ expect(panel.props.showPageTitleMenu).toBe(false);
1249
+ expect(headerActions).toEqual({
1250
+ showPageTitleMenu: false,
1251
+ showShareAction: true,
1252
+ });
1253
+ });
1196
1254
  });
1197
1255
 
1198
1256
  describe("AgentPanel stale lazy chunk recovery", () => {
@@ -0,0 +1,101 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import React, { act } from "react";
4
+ import { createRoot, type Root } from "react-dom/client";
5
+ import { MemoryRouter } from "react-router";
6
+ import { afterEach, describe, expect, it, vi } from "vitest";
7
+
8
+ import { AgentPanel } from "./AgentPanel.js";
9
+
10
+ type MockHeaderProps = {
11
+ renderOverlay?: (props: never) => React.ReactNode;
12
+ };
13
+
14
+ vi.mock("./MultiTabAssistantChat.js", () => ({
15
+ MultiTabAssistantChat: ({ renderOverlay }: MockHeaderProps) =>
16
+ renderOverlay?.({
17
+ tabs: [{ id: "thread-1", label: "Current thread", status: "completed" }],
18
+ activeTabId: "thread-1",
19
+ activeTabMessageCount: 1,
20
+ activeTabIsPersisted: true,
21
+ setActiveTabId: vi.fn(),
22
+ addTab: vi.fn(),
23
+ closeTab: vi.fn(),
24
+ closeOtherTabs: vi.fn(),
25
+ closeAllTabs: vi.fn(),
26
+ clearActiveTab: vi.fn(),
27
+ showHistory: false,
28
+ toggleHistory: vi.fn(),
29
+ tabCount: 1,
30
+ } as never) ?? null,
31
+ }));
32
+
33
+ vi.mock("../sharing/index.js", () => ({
34
+ ShareButton: ({
35
+ triggerContent,
36
+ triggerClassName,
37
+ }: {
38
+ triggerContent?: React.ReactNode;
39
+ triggerClassName?: string;
40
+ }) => (
41
+ <button data-testid="share-trigger" data-trigger-class={triggerClassName}>
42
+ {triggerContent ?? "Share"}
43
+ </button>
44
+ ),
45
+ }));
46
+
47
+ describe("AgentPanel page header share trigger", () => {
48
+ let root: Root | null = null;
49
+ let container: HTMLDivElement | null = null;
50
+
51
+ afterEach(async () => {
52
+ if (root) await act(async () => root?.unmount());
53
+ container?.remove();
54
+ root = null;
55
+ container = null;
56
+ localStorage.clear();
57
+ });
58
+
59
+ it("renders the default icon and host-provided content through ShareButton", async () => {
60
+ container = document.createElement("div");
61
+ document.body.append(container);
62
+ root = createRoot(container);
63
+
64
+ const renderPanel = async (triggerContent?: React.ReactNode) =>
65
+ act(async () => {
66
+ root!.render(
67
+ <MemoryRouter>
68
+ <AgentPanel
69
+ storageKey="page-header-share-test"
70
+ showHeader={false}
71
+ showPageHeader
72
+ pageHeaderShareTriggerContent={triggerContent}
73
+ />
74
+ </MemoryRouter>,
75
+ );
76
+ });
77
+
78
+ await renderPanel();
79
+ const defaultButton = container.querySelector<HTMLButtonElement>(
80
+ '[data-testid="share-trigger"]',
81
+ );
82
+ expect(defaultButton?.querySelector("svg")).not.toBeNull();
83
+ expect(defaultButton?.dataset.triggerClass).toContain("size-8");
84
+
85
+ await renderPanel(
86
+ <span className="flex items-center gap-1.5">
87
+ <svg data-testid="custom-share-icon" aria-hidden="true" />
88
+ <span>Share</span>
89
+ </span>,
90
+ );
91
+ const customButton = container.querySelector<HTMLButtonElement>(
92
+ '[data-testid="share-trigger"]',
93
+ );
94
+ expect(customButton?.textContent).toBe("Share");
95
+ expect(
96
+ customButton?.querySelector('[data-testid="custom-share-icon"]'),
97
+ ).not.toBeNull();
98
+ expect(customButton?.dataset.triggerClass).toContain("w-auto");
99
+ expect(customButton?.dataset.triggerClass).toContain("px-2");
100
+ });
101
+ });
@@ -441,6 +441,30 @@ export function shouldShowAgentPanelPageHeader(
441
441
  );
442
442
  }
443
443
 
444
+ export function resolveAgentPanelPageHeaderActions({
445
+ showPageTitleMenu = true,
446
+ canShareActiveTab,
447
+ }: {
448
+ showPageTitleMenu?: boolean;
449
+ canShareActiveTab: boolean;
450
+ }) {
451
+ return { showPageTitleMenu, showShareAction: canShareActiveTab };
452
+ }
453
+
454
+ export type AgentPanelPageToolbarActions = Pick<
455
+ MultiTabAssistantChatHeaderCallbackProps,
456
+ "activeTabMessageCount" | "clearActiveTab" | "showHistory" | "toggleHistory"
457
+ >;
458
+
459
+ export function resolveAgentPanelPageToolbarSlot(
460
+ slot:
461
+ | React.ReactNode
462
+ | ((actions: AgentPanelPageToolbarActions) => React.ReactNode),
463
+ actions: AgentPanelPageToolbarActions,
464
+ ) {
465
+ return typeof slot === "function" ? slot(actions) : slot;
466
+ }
467
+
444
468
  export function shouldShowAgentPanelCliTabBar(cliTabs: string[]) {
445
469
  return cliTabs.length > 1;
446
470
  }
@@ -583,8 +607,15 @@ export interface AgentPanelProps extends Omit<
583
607
  showPageNewChatButton?: boolean;
584
608
  showPageHeader?: boolean;
585
609
  showPageHeaderWhenEmpty?: boolean;
610
+ /** Hide the default page title menu while keeping the shared page actions. */
611
+ showPageTitleMenu?: boolean;
586
612
  pageHeaderLeadingSlot?: React.ReactNode;
587
- pageToolbarSlot?: React.ReactNode;
613
+ /** Override the shared page header share button's icon and label contents. */
614
+ pageHeaderShareTriggerContent?: React.ReactNode;
615
+ /** Render host page actions alongside Share, with the active chat controls. */
616
+ pageToolbarSlot?:
617
+ | React.ReactNode
618
+ | ((actions: AgentPanelPageToolbarActions) => React.ReactNode);
588
619
  onPageHeaderVisibilityChange?: (visible: boolean) => void;
589
620
  chatOnly?: boolean;
590
621
  agentPageHref?: string;
@@ -778,7 +809,9 @@ function AgentPanelInner({
778
809
  showPageNewChatButton = false,
779
810
  showPageHeader = false,
780
811
  showPageHeaderWhenEmpty = false,
812
+ showPageTitleMenu = true,
781
813
  pageHeaderLeadingSlot,
814
+ pageHeaderShareTriggerContent,
782
815
  pageToolbarSlot,
783
816
  onPageHeaderVisibilityChange,
784
817
  chatOnly = false,
@@ -1694,11 +1727,16 @@ function AgentPanelInner({
1694
1727
  showPageHeaderWhenEmpty,
1695
1728
  activeTabIsPersisted,
1696
1729
  );
1697
- const canShareActiveTab =
1730
+ const canShareActiveTab = Boolean(
1698
1731
  activeTab &&
1699
1732
  (activeTabIsPersisted ||
1700
1733
  activeTabMessageCount > 0 ||
1701
- activeTab.status !== "idle");
1734
+ activeTab.status !== "idle"),
1735
+ );
1736
+ const pageHeaderActions = resolveAgentPanelPageHeaderActions({
1737
+ showPageTitleMenu,
1738
+ canShareActiveTab,
1739
+ });
1702
1740
  const showNewChatAction =
1703
1741
  showPageNewChatButton &&
1704
1742
  shouldShowAgentPanelPageNewChatButton(
@@ -1707,6 +1745,12 @@ function AgentPanelInner({
1707
1745
  activeTabMessageCount,
1708
1746
  activeTabIsPersisted,
1709
1747
  );
1748
+ const toolbarSlot = resolveAgentPanelPageToolbarSlot(pageToolbarSlot, {
1749
+ activeTabMessageCount,
1750
+ clearActiveTab,
1751
+ showHistory,
1752
+ toggleHistory,
1753
+ });
1710
1754
 
1711
1755
  return (
1712
1756
  <>
@@ -1730,62 +1774,64 @@ function AgentPanelInner({
1730
1774
  <h1 className="truncate text-xs font-medium text-foreground">
1731
1775
  {activeTab?.label || t("agentPanel.newChat")}
1732
1776
  </h1>
1733
- <DropdownMenu
1734
- open={headerMenuOpen}
1735
- onOpenChange={setHeaderMenuOpen}
1736
- >
1737
- <DropdownMenuTrigger asChild>
1738
- <button
1739
- type="button"
1740
- data-agent-page-title-menu=""
1741
- aria-label={t("agentPanel.panelOptions")}
1742
- className="flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent/60 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[state=open]:bg-accent data-[state=open]:text-foreground"
1743
- >
1744
- <IconDotsVertical size={14} />
1745
- </button>
1746
- </DropdownMenuTrigger>
1747
- <DropdownMenuContent
1748
- align="start"
1749
- sideOffset={5}
1750
- className="w-44"
1751
- onCloseAutoFocus={(event) => {
1752
- consumeAgentPanelOverlayFocusRestore(
1753
- pendingHeaderOverlayRef,
1754
- event,
1755
- );
1756
- }}
1777
+ {pageHeaderActions.showPageTitleMenu ? (
1778
+ <DropdownMenu
1779
+ open={headerMenuOpen}
1780
+ onOpenChange={setHeaderMenuOpen}
1757
1781
  >
1758
- {toggleHistory ? (
1759
- <DropdownMenuItem
1760
- onSelect={(event) =>
1761
- deferAgentPanelOverlayOpen(
1762
- event,
1763
- closeHeaderMenuForOverlay,
1764
- toggleHistory,
1765
- pendingHeaderOverlayRef,
1766
- )
1767
- }
1782
+ <DropdownMenuTrigger asChild>
1783
+ <button
1784
+ type="button"
1785
+ data-agent-page-title-menu=""
1786
+ aria-label={t("agentPanel.panelOptions")}
1787
+ className="flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent/60 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[state=open]:bg-accent data-[state=open]:text-foreground"
1768
1788
  >
1769
- <IconHistory size={14} className="shrink-0" />
1770
- {showHistory
1771
- ? t("agentPanel.hideChats")
1772
- : t("agentPanel.allChats")}
1773
- </DropdownMenuItem>
1774
- ) : null}
1775
- {activeTabMessageCount > 0 ? (
1776
- <DropdownMenuItem onSelect={clearActiveTab}>
1777
- <IconX size={14} className="shrink-0" />
1778
- {t("agentPanel.clearChat")}
1779
- </DropdownMenuItem>
1780
- ) : null}
1781
- </DropdownMenuContent>
1782
- </DropdownMenu>
1789
+ <IconDotsVertical size={14} />
1790
+ </button>
1791
+ </DropdownMenuTrigger>
1792
+ <DropdownMenuContent
1793
+ align="start"
1794
+ sideOffset={5}
1795
+ className="w-44"
1796
+ onCloseAutoFocus={(event) => {
1797
+ consumeAgentPanelOverlayFocusRestore(
1798
+ pendingHeaderOverlayRef,
1799
+ event,
1800
+ );
1801
+ }}
1802
+ >
1803
+ {toggleHistory ? (
1804
+ <DropdownMenuItem
1805
+ onSelect={(event) =>
1806
+ deferAgentPanelOverlayOpen(
1807
+ event,
1808
+ closeHeaderMenuForOverlay,
1809
+ toggleHistory,
1810
+ pendingHeaderOverlayRef,
1811
+ )
1812
+ }
1813
+ >
1814
+ <IconHistory size={14} className="shrink-0" />
1815
+ {showHistory
1816
+ ? t("agentPanel.hideChats")
1817
+ : t("agentPanel.allChats")}
1818
+ </DropdownMenuItem>
1819
+ ) : null}
1820
+ {activeTabMessageCount > 0 ? (
1821
+ <DropdownMenuItem onSelect={clearActiveTab}>
1822
+ <IconX size={14} className="shrink-0" />
1823
+ {t("agentPanel.clearChat")}
1824
+ </DropdownMenuItem>
1825
+ ) : null}
1826
+ </DropdownMenuContent>
1827
+ </DropdownMenu>
1828
+ ) : null}
1783
1829
  {activeTab?.status === "running" ? (
1784
1830
  <span className="size-1.5 shrink-0 rounded-full bg-muted-foreground/55 animate-pulse motion-reduce:animate-none" />
1785
1831
  ) : null}
1786
1832
  </div>
1787
1833
  <div className="flex shrink-0 items-center gap-1">
1788
- {canShareActiveTab ? (
1834
+ {pageHeaderActions.showShareAction && activeTab ? (
1789
1835
  <ShareButton
1790
1836
  resourceType="chat_thread"
1791
1837
  resourceId={activeTab.id}
@@ -1793,12 +1839,20 @@ function AgentPanelInner({
1793
1839
  resourceTitle={activeTab.label || t("agentPanel.chat")}
1794
1840
  shareUrl={getChatThreadShareUrl(activeTab.id)}
1795
1841
  triggerContent={
1796
- <IconShare3 size={15} aria-hidden="true" />
1842
+ pageHeaderShareTriggerContent ?? (
1843
+ <IconShare3 size={15} aria-hidden="true" />
1844
+ )
1797
1845
  }
1798
- triggerClassName="size-8 p-0 border-0 bg-transparent shadow-none hover:bg-accent/60"
1846
+ triggerClassName={cn(
1847
+ "h-8 border-0 bg-transparent shadow-none hover:bg-accent/60",
1848
+ pageHeaderShareTriggerContent === undefined ||
1849
+ pageHeaderShareTriggerContent === null
1850
+ ? "size-8 p-0"
1851
+ : "w-auto px-2",
1852
+ )}
1799
1853
  />
1800
1854
  ) : null}
1801
- {pageToolbarSlot}
1855
+ {toolbarSlot}
1802
1856
  {showNewChatAction ? (
1803
1857
  <button
1804
1858
  type="button"
@@ -1826,9 +1880,11 @@ function AgentPanelInner({
1826
1880
  headerMenuOpen,
1827
1881
  onPageHeaderVisibilityChange,
1828
1882
  pageHeaderLeadingSlot,
1883
+ pageHeaderShareTriggerContent,
1829
1884
  pageToolbarSlot,
1830
1885
  pendingHeaderOverlayRef,
1831
1886
  setHeaderMenuOpen,
1887
+ showPageTitleMenu,
1832
1888
  showPageNewChatButton,
1833
1889
  t,
1834
1890
  ],
@@ -53,6 +53,8 @@ export type {
53
53
  AssistantChatSubmitResult,
54
54
  AgentChatSurfaceKind,
55
55
  } from "./surface-types.js";
56
+ export type { AgentChatTranscriptLayout } from "../../agentkit/react/transcript-layout.js";
57
+ export type { AgentChatWorkedForRowLayout } from "../../agentkit/react/transcript-layout.js";
56
58
  export type {
57
59
  MultiTabAssistantChatProps,
58
60
  MultiTabAssistantChatHeaderProps,
@@ -26,6 +26,12 @@ import type {
26
26
  import type { ExportedMessageRepository } from "@assistant-ui/react";
27
27
  import type * as React from "react";
28
28
 
29
+ import type { AgentKitWorkedForSummaryRenderProps } from "../../agentkit/react/context.js";
30
+ import type { AgentMessageContentOrderResolver } from "../../agentkit/react/message-content-order.js";
31
+ import type {
32
+ AgentChatTranscriptLayout,
33
+ AgentChatWorkedForRowLayout,
34
+ } from "../../agentkit/react/transcript-layout.js";
29
35
  import type { AssistantChatHistoryConfig } from "./history-types.js";
30
36
  import type { BuilderSetupCardLayout } from "./run-recovery.js";
31
37
 
@@ -236,6 +242,12 @@ export interface AssistantChatProps {
236
242
  /** Optional content rendered in the empty state, below the suggestion
237
243
  * buttons. Unlike `threadFooterSlot` this never survives the first message. */
238
244
  emptyStateFooter?: React.ReactNode;
245
+ /** Optional transcript geometry. Omitted values keep the shared defaults. */
246
+ transcriptLayout?: AgentChatTranscriptLayout;
247
+ /** Optional elapsed-work row geometry. Omitted values keep the shared defaults. */
248
+ workedForRowLayout?: AgentChatWorkedForRowLayout;
249
+ /** Optional duration text for the shared completed-work row. */
250
+ formatWorkedForDuration?: (durationMs: number) => string;
239
251
  /** Whether to show the header bar. Default: true */
240
252
  showHeader?: boolean;
241
253
  /** CSS class for the outer container */
@@ -267,6 +279,12 @@ export interface AssistantChatProps {
267
279
  ) => void;
268
280
  /** Optional content rendered just above the composer input */
269
281
  composerSlot?: React.ReactNode;
282
+ /** Optional shared context banner rendered above the composer input. */
283
+ composerContextBannerSlot?: React.ReactNode;
284
+ /** Optional renderer for the shared work-summary disclosure row. */
285
+ workedForSummarySlot?: React.ComponentType<AgentKitWorkedForSummaryRenderProps>;
286
+ /** Optional ordering policy for assistant message text and attached widgets. */
287
+ resolveMessageContentOrder?: AgentMessageContentOrderResolver;
270
288
  /** App-owned context controller, mounted separately for each chat thread. */
271
289
  composerContextProvider?: React.ComponentType<AssistantChatComposerContextProviderProps>;
272
290
  /** Optional home content rendered above the composer on an empty chat. */
@@ -2698,9 +2698,17 @@ describe("WorkedForSummary", () => {
2698
2698
 
2699
2699
  beforeEach(() => {
2700
2700
  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
2701
+ builderHandoffMocks.useAgentChatContext.mockReturnValue({ items: [] });
2701
2702
  container = document.createElement("div");
2702
2703
  document.body.appendChild(container);
2703
2704
  root = createRoot(container);
2705
+ const render = root.render.bind(root);
2706
+ root.render = (children: ReactNode) =>
2707
+ render(
2708
+ <AgentNativeI18nProvider persistPreference={false}>
2709
+ {children}
2710
+ </AgentNativeI18nProvider>,
2711
+ );
2704
2712
  });
2705
2713
 
2706
2714
  afterEach(() => {
@@ -2740,6 +2748,17 @@ describe("WorkedForSummary", () => {
2740
2748
  response: ["The query returned a 12% increase."],
2741
2749
  },
2742
2750
  }}
2751
+ workedForRowLayout={{
2752
+ chevronSize: 16,
2753
+ iconGap: 4,
2754
+ fontSize: 14,
2755
+ lineHeight: 20,
2756
+ paddingBlock: 12,
2757
+ paddingInline: 12,
2758
+ }}
2759
+ formatWorkedForDuration={(durationMs) =>
2760
+ `${durationMs / 1000} seconds`
2761
+ }
2743
2762
  />,
2744
2763
  );
2745
2764
  });
@@ -2748,6 +2767,8 @@ describe("WorkedForSummary", () => {
2748
2767
  container.querySelectorAll<HTMLButtonElement>("button[aria-expanded]"),
2749
2768
  ).find((button) => button.textContent?.includes("Worked for"));
2750
2769
  expect(workedFor).not.toBeNull();
2770
+ expect(workedFor?.style.fontSize).toBe("14px");
2771
+ expect(workedFor?.textContent).toContain("Worked for 5 seconds");
2751
2772
  act(() => workedFor?.click());
2752
2773
 
2753
2774
  expect(container.textContent).not.toContain("This private thought");
@@ -2795,6 +2816,68 @@ describe("WorkedForSummary", () => {
2795
2816
  expect(container.textContent).toContain("Worked for 5m");
2796
2817
  });
2797
2818
 
2819
+ it("keeps the disclosure chevron before the duration label", () => {
2820
+ act(() => {
2821
+ root.render(
2822
+ <WorkedForSummary durationMs={5_000}>
2823
+ <div>Details</div>
2824
+ </WorkedForSummary>,
2825
+ );
2826
+ });
2827
+
2828
+ const button = container.querySelector("button[aria-expanded]");
2829
+ expect(button?.children[0]?.tagName.toLowerCase()).toBe("svg");
2830
+ expect(button?.children[1]?.textContent).toBe("Worked for 5s");
2831
+ const buttonClasses = button?.className.split(/\s+/) ?? [];
2832
+ expect(buttonClasses).toContain("gap-1");
2833
+ expect(buttonClasses).toContain("p-3");
2834
+ expect(buttonClasses).toContain("text-start");
2835
+ expect(buttonClasses).not.toContain("text-left");
2836
+ const chevron = button?.children[0];
2837
+ expect(chevron?.getAttribute("class")?.split(/\s+/)).toContain(
2838
+ "rtl:-scale-x-100",
2839
+ );
2840
+
2841
+ act(() => button?.click());
2842
+
2843
+ expect(chevron?.getAttribute("class")?.split(/\s+/)).toContain(
2844
+ "rtl:-rotate-90",
2845
+ );
2846
+ });
2847
+
2848
+ it("applies optional geometry to the completed-work disclosure row", () => {
2849
+ act(() => {
2850
+ root.render(
2851
+ <WorkedForSummary
2852
+ durationMs={5_000}
2853
+ layout={{
2854
+ chevronSize: 16,
2855
+ iconGap: 4,
2856
+ fontSize: 14,
2857
+ lineHeight: 20,
2858
+ paddingBlock: 12,
2859
+ paddingInline: 12,
2860
+ }}
2861
+ >
2862
+ <div>Details</div>
2863
+ </WorkedForSummary>,
2864
+ );
2865
+ });
2866
+
2867
+ const button = container.querySelector<HTMLButtonElement>(
2868
+ "button[aria-expanded]",
2869
+ );
2870
+ const icon = button?.querySelector<SVGElement>("svg");
2871
+ expect(button?.style.gap).toBe("4px");
2872
+ expect(button?.style.fontSize).toBe("14px");
2873
+ expect(button?.style.lineHeight).toBe("20px");
2874
+ expect(button?.style.paddingBlock).toBe("12px");
2875
+ expect(button?.style.paddingInline).toBe("12px");
2876
+ expect(button?.style.fontFamily).toBe("inherit");
2877
+ expect(icon?.style.width).toBe("16px");
2878
+ expect(icon?.style.height).toBe("16px");
2879
+ });
2880
+
2798
2881
  it("applies the running shimmer while the work summary is live", () => {
2799
2882
  act(() => {
2800
2883
  root.render(