@agent-native/toolkit 0.217.0 → 0.219.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 (206) hide show
  1. package/agent-native.eject.json +21 -0
  2. package/dist/app/agent-page/AgentContextTab.d.ts.map +1 -1
  3. package/dist/app/agent-page/AgentContextTab.js +1 -0
  4. package/dist/app/agent-page/AgentContextTab.js.map +1 -1
  5. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  6. package/dist/app/agentkit/react/components.js +1 -1
  7. package/dist/app/agentkit/react/components.js.map +1 -1
  8. package/dist/app/agentkit/react/context.d.ts +4 -1
  9. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  10. package/dist/app/agentkit/react/context.js +3 -1
  11. package/dist/app/agentkit/react/context.js.map +1 -1
  12. package/dist/app/agentkit/react/data-chart-layout.d.ts +30 -0
  13. package/dist/app/agentkit/react/data-chart-layout.d.ts.map +1 -0
  14. package/dist/app/agentkit/react/data-chart-layout.js +2 -0
  15. package/dist/app/agentkit/react/data-chart-layout.js.map +1 -0
  16. package/dist/app/agentkit/react/index.d.ts +1 -0
  17. package/dist/app/agentkit/react/index.d.ts.map +1 -1
  18. package/dist/app/agentkit/react/index.js +1 -0
  19. package/dist/app/agentkit/react/index.js.map +1 -1
  20. package/dist/app/agentkit/react/root.d.ts +3 -1
  21. package/dist/app/agentkit/react/root.d.ts.map +1 -1
  22. package/dist/app/agentkit/react/root.js +2 -2
  23. package/dist/app/agentkit/react/root.js.map +1 -1
  24. package/dist/app/agentkit/react/styles.css +31 -6
  25. package/dist/app/agentkit/react/transcript-layout.d.ts +2 -0
  26. package/dist/app/agentkit/react/transcript-layout.d.ts.map +1 -1
  27. package/dist/app/agentkit/react/transcript-layout.js.map +1 -1
  28. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  29. package/dist/app/chat/AgentKitAssistantChat.js +2 -2
  30. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  31. package/dist/app/chat/AgentPanel.js +6 -6
  32. package/dist/app/chat/AgentPanel.js.map +1 -1
  33. package/dist/app/chat/MultiTabAssistantChat.d.ts.map +1 -1
  34. package/dist/app/chat/MultiTabAssistantChat.js +53 -95
  35. package/dist/app/chat/MultiTabAssistantChat.js.map +1 -1
  36. package/dist/app/chat/agentkit-chat/action-widget.d.ts.map +1 -1
  37. package/dist/app/chat/agentkit-chat/action-widget.js +4 -1
  38. package/dist/app/chat/agentkit-chat/action-widget.js.map +1 -1
  39. package/dist/app/chat/agentkit-chat/rail.d.ts +1 -1
  40. package/dist/app/chat/agentkit-chat/rail.d.ts.map +1 -1
  41. package/dist/app/chat/agentkit-chat/rail.js +1 -1
  42. package/dist/app/chat/agentkit-chat/rail.js.map +1 -1
  43. package/dist/app/chat/chat/index.d.ts +2 -1
  44. package/dist/app/chat/chat/index.d.ts.map +1 -1
  45. package/dist/app/chat/chat/index.js +1 -1
  46. package/dist/app/chat/chat/index.js.map +1 -1
  47. package/dist/app/chat/chat/surface-types.d.ts +3 -0
  48. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  49. package/dist/app/chat/chat/surface-types.js.map +1 -1
  50. package/dist/app/chat/chat/tool-call-display.d.ts +5 -1
  51. package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
  52. package/dist/app/chat/chat/tool-call-display.js +6 -4
  53. package/dist/app/chat/chat/tool-call-display.js.map +1 -1
  54. package/dist/app/chat/chat/tool-render-registry.d.ts +4 -0
  55. package/dist/app/chat/chat/tool-render-registry.d.ts.map +1 -1
  56. package/dist/app/chat/chat/tool-render-registry.js.map +1 -1
  57. package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts +3 -1
  58. package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts.map +1 -1
  59. package/dist/app/chat/chat/widgets/DataChartRenderer.js +116 -26
  60. package/dist/app/chat/chat/widgets/DataChartRenderer.js.map +1 -1
  61. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts +3 -1
  62. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts.map +1 -1
  63. package/dist/app/chat/chat/widgets/DataChartWidget.js +21 -2
  64. package/dist/app/chat/chat/widgets/DataChartWidget.js.map +1 -1
  65. package/dist/app/chat/chat/widgets/DataInsightsWidget.d.ts +3 -1
  66. package/dist/app/chat/chat/widgets/DataInsightsWidget.d.ts.map +1 -1
  67. package/dist/app/chat/chat/widgets/DataInsightsWidget.js +2 -2
  68. package/dist/app/chat/chat/widgets/DataInsightsWidget.js.map +1 -1
  69. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts +1 -1
  70. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts.map +1 -1
  71. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js +3 -1
  72. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js.map +1 -1
  73. package/dist/app/chat/chat/widgets/builtin-tool-renderers.d.ts.map +1 -1
  74. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js +2 -2
  75. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js.map +1 -1
  76. package/dist/app/chat/chat-first/chat-history.d.ts +13 -2
  77. package/dist/app/chat/chat-first/chat-history.d.ts.map +1 -1
  78. package/dist/app/chat/chat-first/chat-history.js +24 -3
  79. package/dist/app/chat/chat-first/chat-history.js.map +1 -1
  80. package/dist/app/chat/conversation/AgentConversation.d.ts.map +1 -1
  81. package/dist/app/chat/conversation/AgentConversation.js +6 -1
  82. package/dist/app/chat/conversation/AgentConversation.js.map +1 -1
  83. package/dist/app/chat/ownership-history/ChatOwnerAvatar.d.ts +6 -0
  84. package/dist/app/chat/ownership-history/ChatOwnerAvatar.d.ts.map +1 -0
  85. package/dist/app/chat/ownership-history/ChatOwnerAvatar.js +9 -0
  86. package/dist/app/chat/ownership-history/ChatOwnerAvatar.js.map +1 -0
  87. package/dist/app/chat/ownership-history/index.d.ts +11 -0
  88. package/dist/app/chat/ownership-history/index.d.ts.map +1 -0
  89. package/dist/app/chat/ownership-history/index.js +22 -0
  90. package/dist/app/chat/ownership-history/index.js.map +1 -0
  91. package/dist/app/chat/ownership-history/rows.d.ts +27 -0
  92. package/dist/app/chat/ownership-history/rows.d.ts.map +1 -0
  93. package/dist/app/chat/ownership-history/rows.js +101 -0
  94. package/dist/app/chat/ownership-history/rows.js.map +1 -0
  95. package/dist/app/chat/ownership-history/useOwnershipChatHistory.d.ts +52 -0
  96. package/dist/app/chat/ownership-history/useOwnershipChatHistory.d.ts.map +1 -0
  97. package/dist/app/chat/ownership-history/useOwnershipChatHistory.js +497 -0
  98. package/dist/app/chat/ownership-history/useOwnershipChatHistory.js.map +1 -0
  99. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  100. package/dist/app/i18n/catalogs/ar-SA.js +3 -0
  101. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  102. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  103. package/dist/app/i18n/catalogs/de-DE.js +3 -0
  104. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  105. package/dist/app/i18n/catalogs/en-US.d.ts +3 -0
  106. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  107. package/dist/app/i18n/catalogs/en-US.js +3 -0
  108. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  109. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  110. package/dist/app/i18n/catalogs/es-ES.js +3 -0
  111. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  112. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  113. package/dist/app/i18n/catalogs/fr-FR.js +3 -0
  114. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  115. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  116. package/dist/app/i18n/catalogs/hi-IN.js +3 -0
  117. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  118. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  119. package/dist/app/i18n/catalogs/ja-JP.js +3 -0
  120. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  121. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  122. package/dist/app/i18n/catalogs/ko-KR.js +3 -0
  123. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  124. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  125. package/dist/app/i18n/catalogs/pt-BR.js +3 -0
  126. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  127. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  128. package/dist/app/i18n/catalogs/zh-CN.js +3 -0
  129. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  130. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  131. package/dist/app/i18n/catalogs/zh-TW.js +3 -0
  132. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  133. package/dist/app/shared/CommandMenu.d.ts +2 -0
  134. package/dist/app/shared/CommandMenu.d.ts.map +1 -1
  135. package/dist/app/shared/CommandMenu.js +11 -6
  136. package/dist/app/shared/CommandMenu.js.map +1 -1
  137. package/dist/app/sharing/ShareButton.d.ts.map +1 -1
  138. package/dist/app/sharing/ShareButton.js +9 -8
  139. package/dist/app/sharing/ShareButton.js.map +1 -1
  140. package/dist/chat-history/ChatHistoryList.d.ts +16 -3
  141. package/dist/chat-history/ChatHistoryList.d.ts.map +1 -1
  142. package/dist/chat-history/ChatHistoryList.js +25 -15
  143. package/dist/chat-history/ChatHistoryList.js.map +1 -1
  144. package/dist/chat-history.css +109 -9
  145. package/dist/composer/TiptapComposer.js +1 -1
  146. package/dist/composer/TiptapComposer.js.map +1 -1
  147. package/package.json +10 -3
  148. package/src/app/agent-page/AgentContextTab.spec.ts +50 -2
  149. package/src/app/agent-page/AgentContextTab.tsx +1 -0
  150. package/src/app/agentkit/react/components.interaction.spec.tsx +2 -0
  151. package/src/app/agentkit/react/components.spec.tsx +11 -6
  152. package/src/app/agentkit/react/components.tsx +3 -0
  153. package/src/app/agentkit/react/context.tsx +6 -0
  154. package/src/app/agentkit/react/data-chart-layout.ts +25 -0
  155. package/src/app/agentkit/react/index.ts +1 -0
  156. package/src/app/agentkit/react/root.tsx +4 -0
  157. package/src/app/agentkit/react/styles.css +31 -6
  158. package/src/app/agentkit/react/transcript-layout.ts +2 -0
  159. package/src/app/chat/AgentKitAssistantChat.spec.tsx +201 -27
  160. package/src/app/chat/AgentKitAssistantChat.tsx +3 -0
  161. package/src/app/chat/AgentPanel.tsx +8 -8
  162. package/src/app/chat/MultiTabAssistantChat.spec.tsx +270 -3
  163. package/src/app/chat/MultiTabAssistantChat.tsx +81 -158
  164. package/src/app/chat/agentkit-chat/action-widget.spec.tsx +62 -0
  165. package/src/app/chat/agentkit-chat/action-widget.tsx +4 -0
  166. package/src/app/chat/agentkit-chat/rail.ts +11 -0
  167. package/src/app/chat/chat/index.ts +10 -0
  168. package/src/app/chat/chat/surface-types.ts +3 -0
  169. package/src/app/chat/chat/tool-call-display.spec.tsx +29 -0
  170. package/src/app/chat/chat/tool-call-display.tsx +17 -1
  171. package/src/app/chat/chat/tool-render-registry.tsx +5 -0
  172. package/src/app/chat/chat/widgets/DataChartRenderer.spec.tsx +377 -17
  173. package/src/app/chat/chat/widgets/DataChartRenderer.tsx +232 -63
  174. package/src/app/chat/chat/widgets/DataChartWidget.spec.tsx +67 -0
  175. package/src/app/chat/chat/widgets/DataChartWidget.tsx +39 -5
  176. package/src/app/chat/chat/widgets/DataInsightsWidget.tsx +13 -2
  177. package/src/app/chat/chat/widgets/DataWidgetDisplayControls.tsx +6 -4
  178. package/src/app/chat/chat/widgets/builtin-tool-renderers.tsx +5 -1
  179. package/src/app/chat/chat-first/chat-history.spec.tsx +79 -0
  180. package/src/app/chat/chat-first/chat-history.tsx +75 -22
  181. package/src/app/chat/conversation/AgentConversation.spec.tsx +51 -1
  182. package/src/app/chat/conversation/AgentConversation.tsx +11 -2
  183. package/src/app/chat/ownership-history/ChatOwnerAvatar.tsx +27 -0
  184. package/src/app/chat/ownership-history/index.tsx +67 -0
  185. package/src/app/chat/ownership-history/ownership-history.spec.tsx +3146 -0
  186. package/src/app/chat/ownership-history/rows.ts +156 -0
  187. package/src/app/chat/ownership-history/useOwnershipChatHistory.ts +709 -0
  188. package/src/app/i18n/catalogs/ar-SA.ts +3 -0
  189. package/src/app/i18n/catalogs/de-DE.ts +3 -0
  190. package/src/app/i18n/catalogs/en-US.ts +3 -0
  191. package/src/app/i18n/catalogs/es-ES.ts +3 -0
  192. package/src/app/i18n/catalogs/fr-FR.ts +3 -0
  193. package/src/app/i18n/catalogs/hi-IN.ts +3 -0
  194. package/src/app/i18n/catalogs/ja-JP.ts +3 -0
  195. package/src/app/i18n/catalogs/ko-KR.ts +3 -0
  196. package/src/app/i18n/catalogs/pt-BR.ts +3 -0
  197. package/src/app/i18n/catalogs/zh-CN.ts +3 -0
  198. package/src/app/i18n/catalogs/zh-TW.ts +3 -0
  199. package/src/app/shared/CommandMenu.spec.tsx +175 -6
  200. package/src/app/shared/CommandMenu.tsx +24 -7
  201. package/src/app/sharing/ShareButton.tsx +12 -10
  202. package/src/chat-history/ChatHistoryList.spec.tsx +51 -0
  203. package/src/chat-history/ChatHistoryList.tsx +87 -26
  204. package/src/chat-history/ChatHistoryRailSections.spec.tsx +12 -4
  205. package/src/chat-history.css +109 -9
  206. package/src/composer/TiptapComposer.tsx +1 -1
@@ -34,6 +34,7 @@ import React, { act } from "react";
34
34
  import { flushSync } from "react-dom";
35
35
  import { createRoot, type Root } from "react-dom/client";
36
36
  import { MemoryRouter } from "react-router";
37
+ import { toast } from "sonner";
37
38
  import {
38
39
  afterEach,
39
40
  beforeEach,
@@ -7874,6 +7875,8 @@ describe("MultiTabAssistantChat page overlay", () => {
7874
7875
  describe("MultiTabAssistantChat history popover", () => {
7875
7876
  let container: HTMLDivElement;
7876
7877
  let root: Root;
7878
+ let sharedHistoryRows: ChatThreadSummary[];
7879
+ let mineHistoryRows: ChatThreadSummary[] | undefined;
7877
7880
 
7878
7881
  beforeEach(() => {
7879
7882
  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
@@ -7882,6 +7885,8 @@ describe("MultiTabAssistantChat history popover", () => {
7882
7885
  return 0;
7883
7886
  });
7884
7887
  resetThreadMocks();
7888
+ sharedHistoryRows = [];
7889
+ mineHistoryRows = undefined;
7885
7890
  const now = Date.now();
7886
7891
  threadMocks.activeThreadId = "thread-1";
7887
7892
  const historyThreads: ChatThreadSummary[] = [
@@ -7917,7 +7922,32 @@ describe("MultiTabAssistantChat history popover", () => {
7917
7922
  threadMocks.threads = historyThreads;
7918
7923
  vi.stubGlobal(
7919
7924
  "fetch",
7920
- vi.fn(async () => Response.json({ value: null })),
7925
+ vi.fn(async (input: RequestInfo | URL) => {
7926
+ const url = new URL(String(input), "http://test.local");
7927
+ const ownership = url.searchParams.get("ownership");
7928
+ if (ownership) {
7929
+ const source =
7930
+ ownership === "mine"
7931
+ ? (mineHistoryRows ?? threadMocks.threads).map((thread) => ({
7932
+ ...thread,
7933
+ ownerEmail: "me@example.com",
7934
+ accessBasis: "owner",
7935
+ visibility: "private",
7936
+ }))
7937
+ : sharedHistoryRows;
7938
+ const query = url.searchParams.get("q") ?? "";
7939
+ const matching = source.filter((row) =>
7940
+ row.title.toLowerCase().includes(query.toLowerCase()),
7941
+ );
7942
+ const offset = Number(url.searchParams.get("offset") ?? 0);
7943
+ const limit = Number(url.searchParams.get("limit") ?? 50);
7944
+ return Response.json({
7945
+ threads: matching.slice(offset, offset + limit),
7946
+ hasMore: matching.length > offset + limit,
7947
+ });
7948
+ }
7949
+ return Response.json({ value: null });
7950
+ }),
7921
7951
  );
7922
7952
  window.localStorage.clear();
7923
7953
  window.localStorage.setItem(
@@ -7947,7 +7977,7 @@ describe("MultiTabAssistantChat history popover", () => {
7947
7977
  'button[aria-label="All chats"]',
7948
7978
  );
7949
7979
  expect(historyButton).not.toBeNull();
7950
- act(() => {
7980
+ await act(async () => {
7951
7981
  historyButton!.click();
7952
7982
  });
7953
7983
  }
@@ -7965,13 +7995,224 @@ describe("MultiTabAssistantChat history popover", () => {
7965
7995
  });
7966
7996
  }
7967
7997
 
7998
+ it.each(["local", "owner"] as const)(
7999
+ "keeps the active proven-%s empty chat before persistence and hides inactive blanks",
8000
+ async (proof) => {
8001
+ mineHistoryRows = [];
8002
+ threadMocks.isNewThread.mockImplementation(() => proof === "local");
8003
+ threadMocks.threads = threadMocks.threads.slice(0, 2).map((thread) => ({
8004
+ ...thread,
8005
+ messageCount: 0,
8006
+ ...(proof === "owner"
8007
+ ? {
8008
+ accessBasis: "owner" as const,
8009
+ ownerEmail: "me@example.com",
8010
+ visibility: "private" as const,
8011
+ }
8012
+ : {}),
8013
+ }));
8014
+ await openHistory();
8015
+
8016
+ const mine = container.querySelector(
8017
+ '.an-chat-history__section[data-section-id="mine"]',
8018
+ )!;
8019
+ expect(
8020
+ Array.from(mine.querySelectorAll(".an-chat-history-row__title")).map(
8021
+ (element) => element.textContent,
8022
+ ),
8023
+ ).toEqual(["Active chat"]);
8024
+ expect(mine.querySelector(".an-chat-history-row--active")).not.toBeNull();
8025
+ expect(
8026
+ mine.querySelector(".an-chat-history-row__timestamp")?.textContent,
8027
+ ).toBe("Active");
8028
+ expect(threadMocks.saveThreadData).not.toHaveBeenCalled();
8029
+ },
8030
+ );
8031
+
8032
+ it.each([false, true])(
8033
+ "never recasts an active shared empty chat as Mine (server row loaded: %s)",
8034
+ async (loaded) => {
8035
+ mineHistoryRows = [];
8036
+ threadMocks.isNewThread.mockReturnValue(true);
8037
+ const shared: ChatThreadSummary = {
8038
+ ...threadMocks.threads[0],
8039
+ messageCount: 0,
8040
+ accessBasis: "direct",
8041
+ ownerEmail: "pat@example.com",
8042
+ visibility: "private",
8043
+ };
8044
+ threadMocks.threads = [shared];
8045
+ sharedHistoryRows = loaded ? [shared] : [];
8046
+ await openHistory();
8047
+
8048
+ expect(
8049
+ container.querySelector(
8050
+ '.an-chat-history__section[data-section-id="mine"] .an-chat-history-row',
8051
+ ),
8052
+ ).toBeNull();
8053
+ const sharedSection = container.querySelector(
8054
+ '.an-chat-history__section[data-section-id="shared"]',
8055
+ );
8056
+ expect(
8057
+ sharedSection?.querySelectorAll(".an-chat-history-row").length ?? 0,
8058
+ ).toBe(loaded ? 1 : 0);
8059
+ const trigger =
8060
+ sharedSection?.querySelector<HTMLButtonElement>(
8061
+ ".an-chat-history-row__menu-trigger",
8062
+ ) ?? null;
8063
+ if (!loaded) {
8064
+ expect(trigger).toBeNull();
8065
+ return;
8066
+ }
8067
+ await openRowMenu(trigger!);
8068
+ expect(
8069
+ Array.from(
8070
+ document.body.querySelectorAll(".an-chat-history-row__menu-item"),
8071
+ (element) => element.textContent,
8072
+ ),
8073
+ ).toEqual(["Copy link"]);
8074
+ },
8075
+ );
8076
+
8077
+ it("loads both ownership lists only while open and debounces search to both", async () => {
8078
+ await act(async () => {
8079
+ root.render(<MultiTabAssistantChat storageKey="history-test" />);
8080
+ });
8081
+ const fetchMock = vi.mocked(fetch);
8082
+ const ownershipRequests = () =>
8083
+ fetchMock.mock.calls
8084
+ .map(([input]) => new URL(String(input), "http://test.local"))
8085
+ .filter((url) => url.searchParams.has("ownership"));
8086
+ expect(ownershipRequests()).toHaveLength(0);
8087
+ await openHistory();
8088
+ expect(
8089
+ ownershipRequests().map((url) => url.searchParams.get("ownership")),
8090
+ ).toEqual(["mine", "shared"]);
8091
+ vi.useFakeTimers();
8092
+ try {
8093
+ const input = container.querySelector<HTMLInputElement>(
8094
+ ".an-chat-history__search-input",
8095
+ )!;
8096
+ act(() => {
8097
+ Object.getOwnPropertyDescriptor(
8098
+ window.HTMLInputElement.prototype,
8099
+ "value",
8100
+ )!.set!.call(input, "Other");
8101
+ input.dispatchEvent(new Event("input", { bubbles: true }));
8102
+ });
8103
+ expect(ownershipRequests()).toHaveLength(2);
8104
+ await act(async () => {
8105
+ await vi.advanceTimersByTimeAsync(250);
8106
+ });
8107
+ expect(
8108
+ ownershipRequests()
8109
+ .slice(-2)
8110
+ .map((url) => [
8111
+ url.searchParams.get("ownership"),
8112
+ url.searchParams.get("q"),
8113
+ ]),
8114
+ ).toEqual([
8115
+ ["mine", "Other"],
8116
+ ["shared", "Other"],
8117
+ ]);
8118
+ expect(threadMocks.searchThreads).not.toHaveBeenCalled();
8119
+ } finally {
8120
+ vi.useRealTimers();
8121
+ }
8122
+ });
8123
+
8124
+ it("previews five shared rows without owner pins or actions and switches through history", async () => {
8125
+ sharedHistoryRows = Array.from({ length: 51 }, (_, index) => ({
8126
+ id: `shared-${index}`,
8127
+ title: `Shared ${index}`,
8128
+ preview: "",
8129
+ messageCount: 1,
8130
+ createdAt: 1,
8131
+ updatedAt: 1,
8132
+ scope: null,
8133
+ accessBasis: index === 0 ? "organization" : "direct",
8134
+ ownerEmail: "pat@example.com",
8135
+ visibility: "org",
8136
+ pinnedAt: 10,
8137
+ }));
8138
+ await openHistory();
8139
+ const sharedSection = container.querySelector(
8140
+ '.an-chat-history__section[data-section-id="shared"]',
8141
+ )!;
8142
+ expect(sharedSection.querySelectorAll(".an-chat-history-row")).toHaveLength(
8143
+ 5,
8144
+ );
8145
+ expect(sharedSection.textContent).toContain("pat@example.com");
8146
+ expect(sharedSection.textContent).toContain(
8147
+ "Shared with your organization",
8148
+ );
8149
+ await openRowMenu(
8150
+ sharedSection.querySelector<HTMLButtonElement>(
8151
+ ".an-chat-history-row__menu-trigger",
8152
+ )!,
8153
+ );
8154
+ expect(
8155
+ Array.from(
8156
+ document.body.querySelectorAll(".an-chat-history-row__menu-item"),
8157
+ (element) => element.textContent,
8158
+ ),
8159
+ ).toEqual(["Copy link"]);
8160
+ await act(async () => {
8161
+ document.dispatchEvent(
8162
+ new KeyboardEvent("keydown", { key: "Escape", bubbles: true }),
8163
+ );
8164
+ });
8165
+ expect(
8166
+ sharedSection.querySelector(".an-chat-history-row--pinned"),
8167
+ ).toBeNull();
8168
+ expect(container.querySelector(".an-chat-history-rail__footer")).toBeNull();
8169
+ await act(async () => {
8170
+ sharedSection
8171
+ .querySelector<HTMLButtonElement>(
8172
+ ".an-chat-history-rail__section-toggle",
8173
+ )!
8174
+ .click();
8175
+ });
8176
+ expect(sharedSection.querySelectorAll(".an-chat-history-row")).toHaveLength(
8177
+ 50,
8178
+ );
8179
+ await act(async () => {
8180
+ sharedSection
8181
+ .querySelector<HTMLButtonElement>(".an-chat-history-rail__load-more")!
8182
+ .click();
8183
+ });
8184
+ expect(sharedSection.querySelectorAll(".an-chat-history-row")).toHaveLength(
8185
+ 51,
8186
+ );
8187
+ await act(async () => {
8188
+ sharedSection
8189
+ .querySelector<HTMLButtonElement>(
8190
+ ".an-chat-history-rail__section-toggle",
8191
+ )!
8192
+ .click();
8193
+ });
8194
+ expect(sharedSection.querySelectorAll(".an-chat-history-row")).toHaveLength(
8195
+ 5,
8196
+ );
8197
+ await act(async () => {
8198
+ sharedSection
8199
+ .querySelector<HTMLButtonElement>(".an-chat-history-row__button")!
8200
+ .click();
8201
+ });
8202
+ expect(threadMocks.switchThread).toHaveBeenCalledWith("shared-0");
8203
+ expect(container.querySelector(".an-chat-history")).toBeNull();
8204
+ expect(
8205
+ chatThreadHookMocks.useChatThreads.mock.calls.at(-1)?.[3]?.ownership,
8206
+ ).toBeUndefined();
8207
+ });
8208
+
7968
8209
  it("groups pinned threads into a dedicated section, sorted ahead of the rest", async () => {
7969
8210
  await openHistory();
7970
8211
 
7971
8212
  const labels = Array.from(
7972
8213
  container.querySelectorAll(".an-chat-history__section-label"),
7973
8214
  ).map((el) => el.textContent);
7974
- expect(labels).toEqual(["Pinned"]);
8215
+ expect(labels).toEqual(["Pinned", "My chats"]);
7975
8216
 
7976
8217
  const titles = Array.from(
7977
8218
  container.querySelectorAll(".an-chat-history-row__title"),
@@ -8055,6 +8296,32 @@ describe("MultiTabAssistantChat history popover", () => {
8055
8296
  expect(threadMocks.pinThread).toHaveBeenCalledWith("thread-3", true);
8056
8297
  });
8057
8298
 
8299
+ it("shows the localized save failure when a history pin fails", async () => {
8300
+ const toastError = vi
8301
+ .spyOn(toast, "error")
8302
+ .mockReturnValue("failure-toast");
8303
+ threadMocks.pinThread.mockResolvedValue(false);
8304
+ try {
8305
+ await openHistory();
8306
+ const row = container.querySelectorAll(".an-chat-history-row")[2];
8307
+ await openRowMenu(
8308
+ row.querySelector<HTMLButtonElement>(
8309
+ ".an-chat-history-row__menu-trigger",
8310
+ )!,
8311
+ );
8312
+ const pinItem = Array.from(
8313
+ document.body.querySelectorAll<HTMLButtonElement>(
8314
+ ".an-chat-history-row__menu-item",
8315
+ ),
8316
+ ).find((element) => element.textContent?.includes("Pin to top"))!;
8317
+ await act(async () => pinItem.click());
8318
+ expect(threadMocks.pinThread).toHaveBeenCalledWith("thread-3", true);
8319
+ expect(toastError).toHaveBeenCalledExactlyOnceWith("Save failed");
8320
+ } finally {
8321
+ toastError.mockRestore();
8322
+ }
8323
+ });
8324
+
8058
8325
  it("unpins an already-pinned thread via the row action menu", async () => {
8059
8326
  await openHistory();
8060
8327
 
@@ -33,7 +33,6 @@ import {
33
33
  import {
34
34
  useChatThreads,
35
35
  type ChatThreadScope,
36
- type ChatThreadSummary,
37
36
  } from "@agent-native/core/client/agent-chat";
38
37
  import { agentNativePath, appPath } from "@agent-native/core/client/api-path";
39
38
  import { getBrowserTabId } from "@agent-native/core/client/hooks";
@@ -80,17 +79,13 @@ import React, {
80
79
  useLayoutEffect,
81
80
  useCallback,
82
81
  } from "react";
82
+ import { toast } from "sonner";
83
83
 
84
84
  import {
85
85
  getAgentChatNavigationKey,
86
86
  type AgentChatNavigationKey,
87
87
  } from "./agent-sidebar-events.js";
88
88
  import { AgentKitAssistantChat } from "./AgentKitAssistantChat.js";
89
- import {
90
- ChatHistoryList,
91
- type ChatHistoryItem,
92
- type ChatHistorySection,
93
- } from "./chat/ChatHistoryList.js";
94
89
  import type {
95
90
  AssistantChatProps,
96
91
  AssistantChatHandle,
@@ -98,6 +93,12 @@ import type {
98
93
  AssistantChatSendOptions,
99
94
  } from "./chat/surface-types.js";
100
95
  import { fallbackChatTitle } from "./fallback-chat-title.js";
96
+ import {
97
+ OwnershipChatHistoryRail,
98
+ ownDraftRows,
99
+ useOwnershipChatHistory,
100
+ type OwnDraftRow,
101
+ } from "./ownership-history/index.js";
101
102
 
102
103
  type AgentActionScope = NonNullable<AgentChatMessage["actionScope"]>;
103
104
  const PREFILL_CONTEXT_KEY = "agent-chat-prefill-context";
@@ -488,139 +489,75 @@ function formatThreadTime(
488
489
  }
489
490
 
490
491
  function HistoryPopover({
491
- threads,
492
+ apiUrl,
493
+ scope,
494
+ localMineRows,
492
495
  openTabIds,
493
496
  activeThreadId,
494
- hasMoreThreads = false,
495
- isLoadingMoreThreads = false,
496
- loadError,
497
497
  onSelect,
498
+ onNewChat,
498
499
  onClose,
499
- onLoadMore,
500
- onSearch,
501
500
  onTogglePin,
502
501
  onRename,
502
+ getThreadUrl,
503
503
  popoverAlign = "end",
504
504
  }: {
505
- threads: ChatThreadSummary[];
505
+ apiUrl?: string;
506
+ scope?: ChatThreadScope;
507
+ localMineRows: OwnDraftRow[];
506
508
  openTabIds: Set<string>;
507
509
  activeThreadId: string | null;
508
- hasMoreThreads?: boolean;
509
- isLoadingMoreThreads?: boolean;
510
- loadError?: string | null;
511
510
  onSelect: (id: string) => void;
511
+ onNewChat: () => void;
512
512
  onClose: () => void;
513
- onLoadMore?: () => void;
514
- onSearch?: (query: string) => Promise<ChatThreadSummary[]>;
515
- /** Presence enables the pin/unpin row action. Receives the thread's
516
- * current pinned state so the caller can flip it. */
517
- onTogglePin?: (id: string, pinned: boolean) => void;
518
- /** Presence enables the inline rename row action. */
519
- onRename?: (id: string, nextTitle: string) => void;
513
+ onTogglePin: (id: string, pinned: boolean) => Promise<boolean>;
514
+ onRename: (id: string, nextTitle: string) => Promise<boolean>;
515
+ getThreadUrl: (id: string) => string;
520
516
  popoverAlign?: "start" | "end";
521
517
  }) {
522
518
  const t = useT();
523
519
  const locale = useOptionalLocale()?.locale ?? DEFAULT_LOCALE;
524
520
  const [search, setSearch] = useState("");
525
- const [searchResults, setSearchResults] = useState<
526
- ChatThreadSummary[] | null
527
- >(null);
528
- const [isSearching, setIsSearching] = useState(false);
521
+ const [query, setQuery] = useState("");
529
522
  const inputRef = useRef<HTMLInputElement>(null);
530
- const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);
531
523
 
532
524
  useEffect(() => {
533
- inputRef.current?.focus();
534
- }, []);
535
-
536
- // Debounced server-side search
537
- const searchIdRef = useRef(0);
538
- useEffect(() => {
539
- if (debounceRef.current) clearTimeout(debounceRef.current);
540
- const q = search.trim();
541
- if (!q) {
542
- searchIdRef.current++;
543
- setSearchResults(null);
544
- setIsSearching(false);
545
- return;
546
- }
547
- setIsSearching(true);
548
- const id = ++searchIdRef.current;
549
- debounceRef.current = setTimeout(async () => {
550
- if (onSearch) {
551
- const results = await onSearch(q);
552
- if (id !== searchIdRef.current) return;
553
- setSearchResults(results);
554
- } else {
555
- // Fallback to client-side filtering
556
- setSearchResults(null);
557
- }
558
- setIsSearching(false);
559
- }, 250);
560
- return () => {
561
- if (debounceRef.current) clearTimeout(debounceRef.current);
562
- };
563
- }, [search, onSearch]);
564
-
565
- // Hide empty threads from the history list — except the currently-active
566
- // one. The active thread always belongs in the list so the user can see
567
- // they're in it (the previous filter dropped a brand-new chat the user
568
- // had just opened, making them think their chat had vanished).
569
- const visibleThreads = threads.filter(
570
- (t) => t.messageCount > 0 || t.id === activeThreadId,
571
- );
572
-
573
- const filtered = search.trim()
574
- ? (searchResults ?? visibleThreads).filter(
575
- (t) => t.messageCount > 0 || t.id === activeThreadId,
576
- )
577
- : visibleThreads;
578
-
579
- // Pinned threads always float to the top of `threads` already (see
580
- // `sortThreadSummaries` in use-chat-threads.ts and the matching server
581
- // ORDER BY), but pulling them into their own labeled section — instead of
582
- // just relying on sort order within "All chats" — makes the pin affordance
583
- // visible at a glance, matching common chat-history UX.
584
- const pinnedThreads = filtered.filter((t) => t.pinnedAt != null);
585
- const unpinnedThreads = filtered.filter((t) => t.pinnedAt == null);
586
-
587
- const toHistoryItem = (thread: ChatThreadSummary): ChatHistoryItem => {
588
- const isActive = thread.id === activeThreadId;
589
- const hasTitle = Boolean(thread.title);
590
- const title = thread.title || t("agentChat.history.untitledChat");
591
- return {
592
- id: thread.id,
593
- title,
594
- titleText: title,
525
+ const timer = setTimeout(() => setQuery(search.trim()), 250);
526
+ return () => clearTimeout(timer);
527
+ }, [search]);
528
+
529
+ // This component is mounted only while the popover is open.
530
+ const history = useOwnershipChatHistory({
531
+ apiUrl,
532
+ scope,
533
+ enabled: true,
534
+ query,
535
+ activeId: activeThreadId,
536
+ pinned: true,
537
+ localMineRows,
538
+ mutations: { pin: onTogglePin, rename: onRename },
539
+ getThreadUrl,
540
+ onMutationFailed: () => toast.error(t("agentChat.common.saveFailed")),
541
+ decorateItem: (item, row) => ({
542
+ ...item,
543
+ title: row.title || t("agentChat.history.untitledChat"),
544
+ titleText: row.title || t("agentChat.history.untitledChat"),
595
545
  subtitle:
596
- hasTitle && thread.preview && thread.title !== thread.preview
597
- ? thread.preview
546
+ row.title && row.preview && row.title !== row.preview
547
+ ? row.preview
598
548
  : undefined,
599
- timestamp: isActive
600
- ? t("agentChat.history.active")
601
- : openTabIds.has(thread.id)
602
- ? t("agentChat.history.open")
603
- : formatThreadTime(
604
- thread.updatedAt,
605
- locale,
606
- t("agentChat.history.yesterday"),
607
- ),
608
- pinned: thread.pinnedAt != null,
609
- };
610
- };
611
-
612
- const historySections: ChatHistorySection[] = [
613
- ...(pinnedThreads.length > 0
614
- ? [
615
- {
616
- id: "pinned",
617
- label: t("agentChat.history.pinned"),
618
- items: pinnedThreads.map(toHistoryItem),
619
- },
620
- ]
621
- : []),
622
- { id: "all", items: unpinnedThreads.map(toHistoryItem) },
623
- ];
549
+ timestamp:
550
+ row.id === activeThreadId
551
+ ? t("agentChat.history.active")
552
+ : openTabIds.has(row.id)
553
+ ? t("agentChat.history.open")
554
+ : formatThreadTime(
555
+ row.updatedAt,
556
+ locale,
557
+ t("agentChat.history.yesterday"),
558
+ ),
559
+ }),
560
+ });
624
561
 
625
562
  return (
626
563
  <Popover open onOpenChange={(open) => !open && onClose()}>
@@ -640,46 +577,23 @@ function HistoryPopover({
640
577
  }}
641
578
  className="w-72 rounded-lg p-0"
642
579
  >
643
- <ChatHistoryList
644
- sections={historySections}
580
+ <OwnershipChatHistoryRail
581
+ {...history}
645
582
  activeId={activeThreadId}
583
+ onNewChat={onNewChat}
584
+ showNewChat={false}
646
585
  onSelect={(id) => {
647
586
  onSelect(id);
648
587
  onClose();
649
588
  }}
650
- onTogglePin={
651
- onTogglePin
652
- ? (id) => {
653
- const isPinned =
654
- threads.find((t) => t.id === id)?.pinnedAt != null;
655
- onTogglePin(id, !isPinned);
656
- }
657
- : undefined
658
- }
659
- onRename={onRename}
660
589
  searchValue={search}
661
590
  onSearchChange={setSearch}
662
591
  searchPlaceholder={t("agentChat.history.search")}
663
592
  searchInputRef={inputRef}
664
- loading={isSearching}
665
593
  loadingLabel={t("agentChat.history.searching")}
666
- error={loadError && !search.trim() ? loadError : undefined}
667
594
  emptyLabel={t("agentChat.history.empty")}
668
595
  emptySearchLabel={t("agentChat.history.noMatches")}
669
- footer={
670
- !search.trim() && hasMoreThreads ? (
671
- <button
672
- type="button"
673
- onClick={() => onLoadMore?.()}
674
- disabled={isLoadingMoreThreads}
675
- className="mx-1 mt-1 flex w-[calc(100%-0.5rem)] items-center justify-center rounded-md px-3 py-2 text-xs text-muted-foreground hover:bg-accent hover:text-foreground disabled:cursor-default disabled:opacity-60"
676
- >
677
- {isLoadingMoreThreads
678
- ? t("agentChat.common.loading")
679
- : t("agentChat.history.loadOlder")}
680
- </button>
681
- ) : undefined
682
- }
596
+ className="an-chat-history--capped"
683
597
  />
684
598
  </PopoverContent>
685
599
  </Popover>
@@ -1284,12 +1198,7 @@ export function MultiTabAssistantChat({
1284
1198
  forkThread,
1285
1199
  saveThreadData,
1286
1200
  generateTitle,
1287
- searchThreads,
1288
- loadMoreThreads,
1289
1201
  refreshThreads,
1290
- hasMoreThreads,
1291
- isLoadingMoreThreads,
1292
- threadsLoadError,
1293
1202
  restoredThreadIdOnListFailure,
1294
1203
  evictedThreadIds,
1295
1204
  isNewThread,
@@ -1333,6 +1242,7 @@ export function MultiTabAssistantChat({
1333
1242
  const [showHistory, setShowHistory] = useState(false);
1334
1243
  const [pageOverlayScrolled, setPageOverlayScrolled] = useState(false);
1335
1244
  const newThreadIds = useRef<Set<string>>(new Set());
1245
+ const observedThreadStarts = useRef(new Map<string, number>());
1336
1246
  const [, setThreadPersistenceVersion] = useState(0);
1337
1247
  const handleThreadSnapshotPersisted = useCallback(
1338
1248
  (threadId: string, messageCount: number) => {
@@ -2732,6 +2642,7 @@ export function MultiTabAssistantChat({
2732
2642
  if (!detail) return;
2733
2643
  const { isRunning, tabId } = detail;
2734
2644
  if (!tabId) return;
2645
+ if (isRunning) observedThreadStarts.current.set(tabId, Date.now());
2735
2646
 
2736
2647
  setRunningThreads((prev) => {
2737
2648
  const next = new Set(prev);
@@ -3769,7 +3680,7 @@ export function MultiTabAssistantChat({
3769
3680
  key={tab.id}
3770
3681
  ref={isActive ? activeTabRefCb : undefined}
3771
3682
  className={cn(
3772
- "agent-tab agent-tab-group relative flex items-center rounded-md text-[11px] font-medium shrink-0 min-w-[56px] max-w-[130px]",
3683
+ "agent-tab agent-tab-group relative flex items-center rounded-md text-[11px] font-medium shrink min-w-[56px] max-w-[130px]",
3773
3684
  isActive
3774
3685
  ? "bg-accent text-foreground ring-1 ring-inset ring-border/60 shadow-sm"
3775
3686
  : "text-muted-foreground hover:text-foreground hover:bg-accent/50",
@@ -3871,7 +3782,7 @@ export function MultiTabAssistantChat({
3871
3782
  : undefined
3872
3783
  }
3873
3784
  className={cn(
3874
- "agent-tab agent-tab-group relative flex shrink-0 items-center rounded-md text-[10px] font-medium min-w-[48px] max-w-[130px]",
3785
+ "agent-tab agent-tab-group relative flex shrink items-center rounded-md text-[10px] font-medium min-w-[48px] max-w-[130px]",
3875
3786
  tab.id === activeThreadId
3876
3787
  ? "bg-accent text-foreground"
3877
3788
  : "text-muted-foreground hover:bg-accent hover:text-foreground",
@@ -3939,18 +3850,30 @@ export function MultiTabAssistantChat({
3939
3850
  {/* History popover — rendered inside relative container so positioning works */}
3940
3851
  {showHistory && (
3941
3852
  <HistoryPopover
3942
- threads={threads}
3853
+ apiUrl={apiUrl}
3854
+ scope={isolateHistoryByScope ? (scope ?? undefined) : undefined}
3855
+ localMineRows={ownDraftRows({
3856
+ threads: threads.map((thread) => ({
3857
+ ...thread,
3858
+ messageCount: messageCounts[thread.id] ?? thread.messageCount,
3859
+ })),
3860
+ isNewThread,
3861
+ activeId: activeThreadId,
3862
+ observedThreadStarts: observedThreadStarts.current,
3863
+ })}
3943
3864
  openTabIds={new Set(openTabIds)}
3944
3865
  activeThreadId={activeThreadId}
3945
- hasMoreThreads={hasMoreThreads}
3946
- isLoadingMoreThreads={isLoadingMoreThreads}
3947
- loadError={threadsLoadError}
3948
3866
  onSelect={openFromHistory}
3867
+ onNewChat={addTab}
3949
3868
  onClose={() => setShowHistory(false)}
3950
- onLoadMore={loadMoreThreads}
3951
- onSearch={searchThreads}
3952
3869
  onTogglePin={pinThread}
3953
3870
  onRename={renameThread}
3871
+ getThreadUrl={(threadId) => {
3872
+ if (getThreadPath) return appPath(getThreadPath(threadId));
3873
+ const url = new URL(window.location.href);
3874
+ url.searchParams.set(threadUrlParamName, threadId);
3875
+ return url.toString();
3876
+ }}
3954
3877
  popoverAlign={renderOverlay ? "start" : "end"}
3955
3878
  />
3956
3879
  )}