@agent-native/toolkit 0.198.3 → 0.198.4

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 (144) hide show
  1. package/dist/app/agentkit/react/components.d.ts +4 -1
  2. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  3. package/dist/app/agentkit/react/components.js +12 -4
  4. package/dist/app/agentkit/react/components.js.map +1 -1
  5. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  6. package/dist/app/chat/AgentKitAssistantChat.js +43 -21
  7. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  8. package/dist/app/chat/AgentSidebar.d.ts.map +1 -1
  9. package/dist/app/chat/AgentSidebar.js +2 -0
  10. package/dist/app/chat/AgentSidebar.js.map +1 -1
  11. package/dist/app/chat/FileStorageSetupPopover.js +2 -2
  12. package/dist/app/chat/FileStorageSetupPopover.js.map +1 -1
  13. package/dist/app/chat/MultiTabAssistantChat.js +1 -1
  14. package/dist/app/chat/agentkit-chat/connections.d.ts +1 -4
  15. package/dist/app/chat/agentkit-chat/connections.d.ts.map +1 -1
  16. package/dist/app/chat/agentkit-chat/connections.js +1 -26
  17. package/dist/app/chat/agentkit-chat/connections.js.map +1 -1
  18. package/dist/app/chat/agentkit-chat/history.d.ts +4 -1
  19. package/dist/app/chat/agentkit-chat/history.d.ts.map +1 -1
  20. package/dist/app/chat/agentkit-chat/history.js +17 -5
  21. package/dist/app/chat/agentkit-chat/history.js.map +1 -1
  22. package/dist/app/chat/chat/history-types.d.ts +1 -1
  23. package/dist/app/chat/chat/history-types.d.ts.map +1 -1
  24. package/dist/app/chat/chat-first/copy.d.ts +1 -2
  25. package/dist/app/chat/chat-first/copy.d.ts.map +1 -1
  26. package/dist/app/chat/chat-first/copy.js +1 -121
  27. package/dist/app/chat/chat-first/copy.js.map +1 -1
  28. package/dist/app/chat/chat-first-copy.d.ts +3 -0
  29. package/dist/app/chat/chat-first-copy.d.ts.map +1 -0
  30. package/dist/app/chat/chat-first-copy.js +122 -0
  31. package/dist/app/chat/chat-first-copy.js.map +1 -0
  32. package/dist/app/feature-flags/FeatureFlagsPanel.d.ts.map +1 -1
  33. package/dist/app/feature-flags/FeatureFlagsPanel.js +1 -0
  34. package/dist/app/feature-flags/FeatureFlagsPanel.js.map +1 -1
  35. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  36. package/dist/app/i18n/catalogs/ar-SA.js +2 -1
  37. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  38. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  39. package/dist/app/i18n/catalogs/de-DE.js +2 -1
  40. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  41. package/dist/app/i18n/catalogs/en-US.d.ts +2 -1
  42. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  43. package/dist/app/i18n/catalogs/en-US.js +2 -1
  44. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  45. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  46. package/dist/app/i18n/catalogs/es-ES.js +2 -1
  47. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  48. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  49. package/dist/app/i18n/catalogs/fr-FR.js +2 -1
  50. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  51. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  52. package/dist/app/i18n/catalogs/hi-IN.js +2 -1
  53. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  54. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  55. package/dist/app/i18n/catalogs/ja-JP.js +2 -1
  56. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  57. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  58. package/dist/app/i18n/catalogs/ko-KR.js +2 -1
  59. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  60. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  61. package/dist/app/i18n/catalogs/pt-BR.js +2 -1
  62. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  63. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  64. package/dist/app/i18n/catalogs/zh-CN.js +2 -1
  65. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  66. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  67. package/dist/app/i18n/catalogs/zh-TW.js +2 -1
  68. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  69. package/dist/app/labs/LabsSettings.d.ts.map +1 -1
  70. package/dist/app/labs/LabsSettings.js +7 -3
  71. package/dist/app/labs/LabsSettings.js.map +1 -1
  72. package/dist/app/org/OrgGeneralSection.d.ts.map +1 -1
  73. package/dist/app/org/OrgGeneralSection.js +21 -18
  74. package/dist/app/org/OrgGeneralSection.js.map +1 -1
  75. package/dist/app/org/OrgSwitcher.d.ts.map +1 -1
  76. package/dist/app/org/OrgSwitcher.js +3 -2
  77. package/dist/app/org/OrgSwitcher.js.map +1 -1
  78. package/dist/app/org/workspace-app-links.d.ts +1 -0
  79. package/dist/app/org/workspace-app-links.d.ts.map +1 -1
  80. package/dist/app/org/workspace-app-links.js +3 -0
  81. package/dist/app/org/workspace-app-links.js.map +1 -1
  82. package/dist/app/resources/McpAgentKitConnectionRequest.d.ts +7 -2
  83. package/dist/app/resources/McpAgentKitConnectionRequest.d.ts.map +1 -1
  84. package/dist/app/resources/McpAgentKitConnectionRequest.js +132 -13
  85. package/dist/app/resources/McpAgentKitConnectionRequest.js.map +1 -1
  86. package/dist/icons/ResourceIconPicker.d.ts +6 -1
  87. package/dist/icons/ResourceIconPicker.d.ts.map +1 -1
  88. package/dist/icons/ResourceIconPicker.js +45 -6
  89. package/dist/icons/ResourceIconPicker.js.map +1 -1
  90. package/package.json +6 -4
  91. package/src/app/agentkit/react/components.spec.tsx +3 -3
  92. package/src/app/agentkit/react/components.tsx +22 -4
  93. package/src/app/agentkit/react/lifecycle.spec.tsx +42 -0
  94. package/src/app/chat/AgentKitAssistantChat.spec.tsx +114 -11
  95. package/src/app/chat/AgentKitAssistantChat.tsx +78 -17
  96. package/src/app/chat/AgentPanel.header.spec.ts +59 -22
  97. package/src/app/chat/AgentSidebar.tsx +2 -0
  98. package/src/app/chat/FileStorageSetupPopover.tsx +2 -2
  99. package/src/app/chat/MultiTabAssistantChat.messageCount.spec.tsx +3 -1
  100. package/src/app/chat/MultiTabAssistantChat.spec.tsx +8 -4
  101. package/src/app/chat/MultiTabAssistantChat.tsx +1 -1
  102. package/src/app/chat/agentkit-chat/action-widget.spec.tsx +5 -17
  103. package/src/app/chat/agentkit-chat/connections.spec.tsx +137 -0
  104. package/src/app/chat/agentkit-chat/connections.ts +2 -37
  105. package/src/app/chat/agentkit-chat/history.tsx +23 -8
  106. package/src/app/chat/chat/history-types.ts +1 -1
  107. package/src/app/chat/chat/tool-call-display.spec.tsx +2 -2
  108. package/src/app/chat/chat/widgets/record-change-widget.spec.tsx +20 -29
  109. package/src/app/chat/chat-first/copy.ts +1 -133
  110. package/src/app/chat/chat-first-copy.ts +133 -0
  111. package/src/app/chat/composer/use-agentkit-capabilities.hardening.spec.tsx +3 -2
  112. package/src/app/feature-flags/FeatureFlagsPanel.tsx +1 -0
  113. package/src/app/i18n/catalogs/ar-SA.ts +2 -1
  114. package/src/app/i18n/catalogs/de-DE.ts +3 -1
  115. package/src/app/i18n/catalogs/en-US.ts +2 -1
  116. package/src/app/i18n/catalogs/es-ES.ts +2 -1
  117. package/src/app/i18n/catalogs/fr-FR.ts +3 -1
  118. package/src/app/i18n/catalogs/hi-IN.ts +2 -1
  119. package/src/app/i18n/catalogs/ja-JP.ts +2 -1
  120. package/src/app/i18n/catalogs/ko-KR.ts +2 -1
  121. package/src/app/i18n/catalogs/pt-BR.ts +3 -1
  122. package/src/app/i18n/catalogs/zh-CN.ts +2 -1
  123. package/src/app/i18n/catalogs/zh-TW.ts +2 -1
  124. package/src/app/integrations/IntegrationDetailPage.spec.tsx +1 -1
  125. package/src/app/labs/LabsSettings.tsx +46 -10
  126. package/src/app/observability/ThumbsFeedback.spec.tsx +15 -0
  127. package/src/app/onboarding/FirstRunOnboarding.spec.tsx +4 -0
  128. package/src/app/org/OrgGeneralSection.tsx +32 -22
  129. package/src/app/org/OrgSwitcher.tsx +3 -2
  130. package/src/app/org/workspace-app-links.spec.ts +4 -0
  131. package/src/app/org/workspace-app-links.ts +8 -1
  132. package/src/app/resources/McpAgentKitConnectionRequest.spec.tsx +371 -2
  133. package/src/app/resources/McpAgentKitConnectionRequest.tsx +171 -11
  134. package/src/app/settings/SettingsTabsPage.spec.tsx +4 -2
  135. package/src/app/settings/shell/SettingsShell.spec.tsx +14 -8
  136. package/src/app/settings/shell/pages/app-group-pages.spec.tsx +35 -5
  137. package/src/app/settings/shell/pages/resource-pages.spec.tsx +3 -0
  138. package/src/app/settings/usage/UsagePage.spec.tsx +3 -3
  139. package/src/app/shared/lazy-chunk-error-boundary.spec.tsx +2 -0
  140. package/src/app-shell/sidebar-overflow.spec.tsx +4 -1
  141. package/src/design-tweaks/visual-style-controls.spec.tsx +38 -0
  142. package/src/editor/useCollabReconcile.concurrent.spec.ts +2 -1
  143. package/src/icons/ResourceIconPicker.spec.tsx +270 -0
  144. package/src/icons/ResourceIconPicker.tsx +72 -4
@@ -234,6 +234,18 @@ vi.mock("../agentkit/react/root.js", async () => {
234
234
  return {
235
235
  AgentKitRoot: (props: any) => {
236
236
  chatMocks.rootProps = props;
237
+ if (chatMocks.useRealRoot) {
238
+ return React.createElement(actual.AgentKitRoot, {
239
+ ...props,
240
+ ...(chatMocks.realComposerController
241
+ ? {
242
+ controller: chatMocks.realComposerController,
243
+ transport: undefined,
244
+ endpoint: undefined,
245
+ }
246
+ : {}),
247
+ });
248
+ }
237
249
  if (chatMocks.realComposerController) {
238
250
  return React.createElement(AgentKitProvider, {
239
251
  ...props,
@@ -747,6 +759,29 @@ afterEach(async () => {
747
759
  });
748
760
 
749
761
  describe("AgentKitAssistantChat host behavior", () => {
762
+ it("shows a retry when chat history fails to load", async () => {
763
+ const retryHistory = vi.fn();
764
+ chatMocks.history = {
765
+ historyLoadFailed: true,
766
+ isRetryingHistory: false,
767
+ retryHistory,
768
+ };
769
+
770
+ await mount(baseProps());
771
+
772
+ expect(container.querySelector('[role="alert"]')?.textContent).toContain(
773
+ "agentChat.message.historyUnavailable",
774
+ );
775
+ const retry = [...container.querySelectorAll("button")].find(
776
+ (button) => button.textContent === "agentChat.common.retry",
777
+ );
778
+ expect(retry).toBeDefined();
779
+
780
+ await act(async () => retry?.click());
781
+
782
+ expect(retryHistory).toHaveBeenCalledOnce();
783
+ });
784
+
750
785
  it("shows Thinking in the transcript while a submitted user message is pending", async () => {
751
786
  chatMocks.history = { isSubmissionInFlight: true };
752
787
  chatMocks.thread.messages = [
@@ -759,6 +794,8 @@ describe("AgentKitAssistantChat host behavior", () => {
759
794
  ];
760
795
  await mount(baseProps());
761
796
 
797
+ expect(chatMocks.composerProps.disabled).toBe(false);
798
+ expect(chatMocks.composerProps.submissionDisabled).toBe(true);
762
799
  expect(container.querySelector('[role="status"]')?.textContent).toBe(
763
800
  "agentChat.status.thinking",
764
801
  );
@@ -847,6 +884,29 @@ describe("AgentKitAssistantChat host behavior", () => {
847
884
  return client;
848
885
  }
849
886
 
887
+ it("keeps the real editor editable while a submission is in flight", async () => {
888
+ chatMocks.useRealChat = true;
889
+ chatMocks.history = { isSubmissionInFlight: true };
890
+ await useRealComposer();
891
+ await mount(baseProps({ showModelSelector: false }));
892
+
893
+ const editor = container.querySelector<HTMLElement>(
894
+ '[contenteditable="true"]',
895
+ );
896
+ expect(editor).not.toBeNull();
897
+ expect(chatMocks.composerProps.submissionDisabled).toBe(true);
898
+
899
+ await act(async () =>
900
+ chatMocks.composerProps.composerRef.current.setText("Keep this draft"),
901
+ );
902
+ expect(editor?.textContent).toBe("Keep this draft");
903
+ expect(
904
+ container.querySelector<HTMLButtonElement>(
905
+ '[data-agent-composer-slot="send-button"]',
906
+ )?.disabled,
907
+ ).toBe(true);
908
+ });
909
+
850
910
  it.each(["default", "compact"] as const)(
851
911
  "clears the real %s composer before startRun resolves and preserves the next draft",
852
912
  async (composerLayoutVariant) => {
@@ -855,12 +915,13 @@ describe("AgentKitAssistantChat host behavior", () => {
855
915
  chatMocks.useRealChat = true;
856
916
  const client = await useRealComposer(startRun);
857
917
  try {
858
- await mount(
859
- baseProps({
860
- composerLayoutVariant,
861
- showModelSelector: false,
862
- }),
863
- );
918
+ const props = baseProps({
919
+ composerLayoutVariant,
920
+ showModelSelector: false,
921
+ });
922
+ const openThread = vi.spyOn(client, "openThread");
923
+ chatMocks.useRealRoot = true;
924
+ await mount(props);
864
925
  container.style.width =
865
926
  composerLayoutVariant === "compact" ? "320px" : "960px";
866
927
  const composer = chatMocks.composerProps.composerRef.current;
@@ -884,6 +945,19 @@ describe("AgentKitAssistantChat host behavior", () => {
884
945
  expect(client.getThread(chatMocks.threadId).activeRunIds).toEqual([]);
885
946
  expect(editor.textContent).toBe("");
886
947
 
948
+ await act(async () =>
949
+ root.render(<AgentKitAssistantChat {...props} isNewThread={false} />),
950
+ );
951
+ await flush();
952
+
953
+ expect(chatMocks.rootProps.load).toBe("manual");
954
+ expect(openThread).not.toHaveBeenCalled();
955
+ expect(
956
+ container
957
+ .querySelector<HTMLElement>("[contenteditable]")
958
+ ?.getAttribute("contenteditable"),
959
+ ).toBe("true");
960
+
887
961
  await act(async () => composer.setText("Next draft"));
888
962
  await act(async () => started.resolve({ runId: "run-latency" }));
889
963
 
@@ -1585,7 +1659,7 @@ describe("AgentKitAssistantChat host behavior", () => {
1585
1659
  expect(chatMocks.appState.has(stateKey!)).toBe(false);
1586
1660
  });
1587
1661
 
1588
- it("keeps a queued follow-up queued if its run finishes during provider discovery", async () => {
1662
+ it("preserves queued intent when its run finishes during provider discovery", async () => {
1589
1663
  chatMocks.readiness = {
1590
1664
  canChat: false,
1591
1665
  missing: false,
@@ -1611,12 +1685,27 @@ describe("AgentKitAssistantChat host behavior", () => {
1611
1685
  [],
1612
1686
  [],
1613
1687
  { intent: "queued" },
1688
+ async () => ({}),
1614
1689
  );
1615
1690
  });
1616
1691
 
1617
1692
  expect(chatMocks.control.sendMessage).not.toHaveBeenCalled();
1618
1693
  expect(chatMocks.control.queueMessage).not.toHaveBeenCalled();
1619
1694
 
1695
+ const stateKey = [...chatMocks.appState.keys()].find((key) =>
1696
+ key.startsWith("agentkit-deferred-provider-submissions:"),
1697
+ );
1698
+ expect(chatMocks.appState.get(stateKey!)).toMatchObject({
1699
+ submissions: [
1700
+ {
1701
+ composerOptions: {
1702
+ intent: "queued",
1703
+ queuedWhileRunActive: true,
1704
+ },
1705
+ },
1706
+ ],
1707
+ });
1708
+
1620
1709
  chatMocks.thread.activeRunIds = [];
1621
1710
  chatMocks.thread.runs["analytics-run"].status = "completed";
1622
1711
  chatMocks.readiness = {
@@ -1640,6 +1729,7 @@ describe("AgentKitAssistantChat host behavior", () => {
1640
1729
  }),
1641
1730
  );
1642
1731
  expect(chatMocks.control.sendMessage).not.toHaveBeenCalled();
1732
+ expect(chatMocks.appState.has(stateKey!)).toBe(false);
1643
1733
  });
1644
1734
 
1645
1735
  it("keeps the draft rejected if provider status becomes missing before submit", async () => {
@@ -3338,11 +3428,11 @@ describe("AgentKitAssistantChat host behavior", () => {
3338
3428
  chatMocks.thread.activeRunIds = ["run-1"];
3339
3429
  await mount(baseProps());
3340
3430
 
3341
- await act(async () => {
3342
- await chatMocks.resumeProps.onMessageResume({
3343
- message: "Continue after connecting the integration.",
3344
- });
3431
+ const resume = chatMocks.resumeProps.onMessageResume({
3432
+ message: "Continue after connecting the integration.",
3345
3433
  });
3434
+ expect(resume).toBeInstanceOf(Promise);
3435
+ await act(async () => resume);
3346
3436
  expect(chatMocks.control.queueMessage).toHaveBeenCalledWith(
3347
3437
  expect.objectContaining({
3348
3438
  text: "Continue after connecting the integration.",
@@ -3382,6 +3472,19 @@ describe("AgentKitAssistantChat host behavior", () => {
3382
3472
  expect(chatMocks.control.sendMessage).not.toHaveBeenCalled();
3383
3473
  });
3384
3474
 
3475
+ it("keeps failed integration prompt submissions resumable", async () => {
3476
+ chatMocks.thread.activeRunIds = ["run-1"];
3477
+ const submissionError = new Error("Temporary send failure");
3478
+ chatMocks.control.queueMessage.mockRejectedValueOnce(submissionError);
3479
+ await mount(baseProps());
3480
+
3481
+ await expect(
3482
+ chatMocks.resumeProps.onMessageResume({
3483
+ message: "Continue after OAuth.",
3484
+ }),
3485
+ ).rejects.toBe(submissionError);
3486
+ });
3487
+
3385
3488
  it("shows the missing-final-response warning from recovered run metadata", async () => {
3386
3489
  chatMocks.thread.messages = [
3387
3490
  {
@@ -460,6 +460,15 @@ type AgentKitSuggestionSubmitOptions = PromptComposerSubmitOptions & {
460
460
  queuedWhileRunActive?: boolean;
461
461
  };
462
462
 
463
+ function preserveQueuedIntent(
464
+ preparedOptions: PromptComposerSubmitOptions,
465
+ originalOptions: PromptComposerSubmitOptions,
466
+ ): PromptComposerSubmitOptions {
467
+ return originalOptions.intent === "queued"
468
+ ? { ...preparedOptions, intent: "queued" }
469
+ : preparedOptions;
470
+ }
471
+
463
472
  function captureQueuedRunState(
464
473
  options: PromptComposerSubmitOptions,
465
474
  runWasActive: boolean,
@@ -594,8 +603,13 @@ export const AgentKitAssistantChat = forwardRef<
594
603
  : readAgentKitThreadHandoffSnapshot(
595
604
  createAgentKitThreadHandoffKey(props, threadId),
596
605
  );
606
+ const [manualLoadThreadId, setManualLoadThreadId] = useState<string | null>(
607
+ () => (props.isNewThread ? threadId : null),
608
+ );
609
+ const hasManualLoadForCurrentThread =
610
+ props.isNewThread || manualLoadThreadId === threadId;
597
611
  const [threadRestore, setThreadRestore] = useState<ThreadRestoreState>(() =>
598
- props.isThreadStateLoading || !props.isNewThread
612
+ props.isThreadStateLoading || !hasManualLoadForCurrentThread
599
613
  ? { status: "loading" }
600
614
  : { status: "ready" },
601
615
  );
@@ -611,15 +625,25 @@ export const AgentKitAssistantChat = forwardRef<
611
625
  setRestoreRetryLoadPhase("idle");
612
626
  setRestoreRetryThreadId(null);
613
627
  }, []);
628
+ useEffect(() => {
629
+ setManualLoadThreadId((current) =>
630
+ props.isNewThread ? threadId : current === threadId ? current : null,
631
+ );
632
+ }, [props.isNewThread, threadId]);
614
633
  useEffect(() => {
615
634
  setThreadRestore(
616
- props.isThreadStateLoading || !props.isNewThread
635
+ props.isThreadStateLoading || !hasManualLoadForCurrentThread
617
636
  ? { status: "loading" }
618
637
  : { status: "ready" },
619
638
  );
620
639
  setRestoreRetryLoadPhase("idle");
621
640
  setRestoreRetryThreadId(null);
622
- }, [props.isNewThread, props.isThreadStateLoading, threadId]);
641
+ }, [
642
+ hasManualLoadForCurrentThread,
643
+ props.isNewThread,
644
+ props.isThreadStateLoading,
645
+ threadId,
646
+ ]);
623
647
  const onThreadRestoreLoadError = useCallback(
624
648
  (error: unknown) => {
625
649
  const record = asRecord(error);
@@ -989,7 +1013,7 @@ export const AgentKitAssistantChat = forwardRef<
989
1013
  ]);
990
1014
  const agentKitLoad =
991
1015
  props.isThreadStateLoading ||
992
- props.isNewThread ||
1016
+ hasManualLoadForCurrentThread ||
993
1017
  (restoreRetryThreadId === threadId && restoreRetryLoadPhase === "release")
994
1018
  ? "manual"
995
1019
  : "auto";
@@ -2204,7 +2228,7 @@ const AgentKitAssistantChatBody = forwardRef<
2204
2228
  try {
2205
2229
  const preparedOptions = prepare ? await prepare() : composerOptions;
2206
2230
  const submittedOptions = captureQueuedRunState(
2207
- preparedOptions,
2231
+ preserveQueuedIntent(preparedOptions, composerOptions),
2208
2232
  runWasActiveAtSubmit,
2209
2233
  );
2210
2234
  const result = await submit(
@@ -2231,7 +2255,10 @@ const AgentKitAssistantChatBody = forwardRef<
2231
2255
  text,
2232
2256
  files,
2233
2257
  references,
2234
- captureQueuedRunState(preparedOptions, runWasActiveAtSubmit),
2258
+ captureQueuedRunState(
2259
+ preserveQueuedIntent(preparedOptions, composerOptions),
2260
+ runWasActiveAtSubmit,
2261
+ ),
2235
2262
  );
2236
2263
  } catch (error) {
2237
2264
  dispatchSetupRequiredEvent(error, props.tabId, threadId);
@@ -2552,11 +2579,19 @@ const AgentKitAssistantChatBody = forwardRef<
2552
2579
  [isThreadRunning, submit],
2553
2580
  );
2554
2581
  const resumeIntegrationPrompt = useCallback(
2555
- (message: string) => {
2556
- if (props.isActiveComposer === false) return;
2557
- void send(message).catch((error) => {
2582
+ async (message: string) => {
2583
+ if (props.isActiveComposer === false) {
2584
+ throw new Error("Cannot resume a request in an inactive chat.");
2585
+ }
2586
+ try {
2587
+ const result = await send(message);
2588
+ if (result.status === "rejected") {
2589
+ throw new Error(`Chat resume was rejected: ${result.reason}.`);
2590
+ }
2591
+ } catch (error) {
2558
2592
  dispatchSetupRequiredEvent(error, props.tabId, threadId);
2559
- });
2593
+ throw error;
2594
+ }
2560
2595
  },
2561
2596
  [props.isActiveComposer, props.tabId, send, threadId],
2562
2597
  );
@@ -2775,15 +2810,19 @@ const AgentKitAssistantChatBody = forwardRef<
2775
2810
  { threadId: targetThreadId, runId, requestId },
2776
2811
  request,
2777
2812
  ) => {
2778
- if (targetThreadId !== threadId) return;
2813
+ if (targetThreadId !== threadId) {
2814
+ throw new Error(
2815
+ "Cannot resume a connection request in another chat.",
2816
+ );
2817
+ }
2779
2818
  return control.resolveConnectionRequest(runId, requestId, {
2780
2819
  status: "connected",
2781
2820
  message: request.message,
2782
2821
  });
2783
2822
  }}
2784
- onMessageResume={(request) => {
2785
- resumeIntegrationPrompt(request.message);
2786
- }}
2823
+ onMessageResume={(request) =>
2824
+ resumeIntegrationPrompt(request.message)
2825
+ }
2787
2826
  />
2788
2827
  )}
2789
2828
  <RunStuckBanner
@@ -2805,6 +2844,24 @@ const AgentKitAssistantChatBody = forwardRef<
2805
2844
  void sendRecoveryMessage(RECOVERY_CONTINUE_PROMPT, "continue")
2806
2845
  }
2807
2846
  />
2847
+ {history?.historyLoadFailed ? (
2848
+ <div
2849
+ role="alert"
2850
+ className="mx-3 mb-2 flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2 text-xs"
2851
+ >
2852
+ <span className="text-muted-foreground">
2853
+ {t("agentChat.message.historyUnavailable")}
2854
+ </span>
2855
+ <button
2856
+ type="button"
2857
+ onClick={history.retryHistory}
2858
+ disabled={history.isRetryingHistory}
2859
+ className="shrink-0 font-medium text-foreground hover:underline disabled:cursor-not-allowed disabled:opacity-50"
2860
+ >
2861
+ {t("agentChat.common.retry")}
2862
+ </button>
2863
+ </div>
2864
+ ) : null}
2808
2865
  <AgentKitChat
2809
2866
  className={props.className}
2810
2867
  composerProps={{ attachmentsEnabled: fileStorageConfigured }}
@@ -3668,12 +3725,12 @@ function AgentKitComposerSurface({
3668
3725
  disabled={
3669
3726
  (!canChat && !providerSubmissionPending) ||
3670
3727
  props.composerDisabled ||
3671
- isRestoring ||
3672
- isSubmissionInFlight
3728
+ isRestoring
3673
3729
  }
3674
3730
  submissionDisabled={
3675
3731
  (!canChat && !providerSubmissionPending) ||
3676
- props.composerSubmissionDisabled === true
3732
+ props.composerSubmissionDisabled === true ||
3733
+ isSubmissionInFlight
3677
3734
  }
3678
3735
  onDisabledClick={
3679
3736
  props.composerDisabled || !setupMissing
@@ -4128,6 +4185,10 @@ function AgentKitConnectionRequest({
4128
4185
  <McpAgentKitConnectionRequestCard
4129
4186
  provider={value.provider}
4130
4187
  detail={value.detail}
4188
+ reason={value.reason}
4189
+ status={value.status}
4190
+ appId={value.appId}
4191
+ source={value.source}
4131
4192
  target={{ threadId, runId, requestId: value.id }}
4132
4193
  onConnected={() =>
4133
4194
  control.resolveConnectionRequest(runId, value.id, {
@@ -2,16 +2,16 @@
2
2
 
3
3
  import { readFileSync } from "node:fs";
4
4
 
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuTrigger,
9
+ } from "@agent-native/toolkit/ui/dropdown-menu";
5
10
  import React, { act } from "react";
6
11
  import { createRoot, type Root } from "react-dom/client";
7
12
  import { MemoryRouter, useLocation } from "react-router";
8
13
  import { describe, expect, it, vi } from "vitest";
9
14
 
10
- import {
11
- DropdownMenu,
12
- DropdownMenuContent,
13
- DropdownMenuTrigger,
14
- } from "../../ui/dropdown-menu.js";
15
15
  import {
16
16
  AgentChatSurface,
17
17
  AgentPanelFullViewMenuItem,
@@ -51,6 +51,13 @@ import {
51
51
  preloadAgentChatSurface,
52
52
  } from "./AgentSidebar.js";
53
53
 
54
+ function readSource(
55
+ path: string,
56
+ _options?: "utf8" | { encoding: "utf8" },
57
+ ): string {
58
+ return readFileSync(path, "utf8").replace(/\r\n/g, "\n");
59
+ }
60
+
54
61
  describe("AgentPanel compatibility exports", () => {
55
62
  it("preserves the legacy sidebar entry point", () => {
56
63
  expect(LegacyAgentSidebar).toBe(AgentSidebar);
@@ -60,7 +67,7 @@ describe("AgentPanel compatibility exports", () => {
60
67
  });
61
68
 
62
69
  it("uses a stable-ref link in the full-view menu item", () => {
63
- const source = readFileSync("src/app/chat/AgentPanel.tsx", "utf8").replace(
70
+ const source = readSource("src/app/chat/AgentPanel.tsx", "utf8").replace(
64
71
  /\s+/g,
65
72
  " ",
66
73
  );
@@ -150,7 +157,7 @@ describe("AgentPanel fullscreen menu", () => {
150
157
 
151
158
  describe("AgentPanel suggestion placement", () => {
152
159
  it("forwards explicit placement and defaults to context chips", () => {
153
- const source = readFileSync("src/app/chat/AgentPanel.tsx", {
160
+ const source = readSource("src/app/chat/AgentPanel.tsx", {
154
161
  encoding: "utf8",
155
162
  });
156
163
 
@@ -323,7 +330,7 @@ describe("AgentPanel header tab visibility", () => {
323
330
  });
324
331
 
325
332
  it("exposes page header composition without moving it into app chrome", () => {
326
- const source = readFileSync("src/app/chat/AgentPanel.tsx", "utf8");
333
+ const source = readSource("src/app/chat/AgentPanel.tsx", "utf8");
327
334
 
328
335
  expect(source).toContain('data-agent-page-chat-header=""');
329
336
  expect(source).toContain("pageHeaderLeadingSlot");
@@ -699,7 +706,7 @@ describe("AgentPanel Integrations link", () => {
699
706
  });
700
707
 
701
708
  it("sits right after Open full view and shares its separator", () => {
702
- const source = readFileSync("src/app/chat/AgentPanel.tsx", {
709
+ const source = readSource("src/app/chat/AgentPanel.tsx", {
703
710
  encoding: "utf8",
704
711
  });
705
712
  const overflowMenu = source.slice(
@@ -820,6 +827,36 @@ describe("AgentSidebar composer focus", () => {
820
827
  }
821
828
  });
822
829
 
830
+ it("stops retrying when the document is torn down", () => {
831
+ vi.useFakeTimers();
832
+ const previousRequestAnimationFrame = window.requestAnimationFrame;
833
+ const frames: Array<FrameRequestCallback> = [];
834
+ const panel = document.createElement("div");
835
+ panel.className = "agent-sidebar-panel";
836
+ panel.dataset.agentSidebarState = "open";
837
+ document.body.appendChild(panel);
838
+
839
+ window.requestAnimationFrame = ((callback: FrameRequestCallback) => {
840
+ frames.push(callback);
841
+ return frames.length;
842
+ }) as typeof window.requestAnimationFrame;
843
+
844
+ try {
845
+ focusAgentChat();
846
+ frames[0]!(0);
847
+ vi.stubGlobal("document", undefined);
848
+
849
+ expect(() => vi.advanceTimersByTime(50)).not.toThrow();
850
+ expect(vi.getTimerCount()).toBe(0);
851
+ } finally {
852
+ vi.unstubAllGlobals();
853
+ window.requestAnimationFrame = previousRequestAnimationFrame;
854
+ panel.remove();
855
+ vi.clearAllTimers();
856
+ vi.useRealTimers();
857
+ }
858
+ });
859
+
823
860
  it("focuses a frame-owned composer", () => {
824
861
  const previousRequestAnimationFrame = window.requestAnimationFrame;
825
862
  const frames: Array<FrameRequestCallback> = [];
@@ -914,10 +951,10 @@ describe("AgentPanel header overflow actions", () => {
914
951
  });
915
952
 
916
953
  it("keeps width and full-view actions out of the icon row", () => {
917
- const source = readFileSync("src/app/chat/AgentPanel.tsx", {
954
+ const source = readSource("src/app/chat/AgentPanel.tsx", {
918
955
  encoding: "utf8",
919
956
  });
920
- const sidebarSource = readFileSync("src/app/chat/AgentSidebar.tsx", {
957
+ const sidebarSource = readSource("src/app/chat/AgentSidebar.tsx", {
921
958
  encoding: "utf8",
922
959
  });
923
960
  const headerActions = source.slice(
@@ -957,7 +994,7 @@ describe("AgentPanel header overflow actions", () => {
957
994
  });
958
995
 
959
996
  it("keeps the overflow menu scrollable within the viewport", () => {
960
- const source = readFileSync("src/app/chat/AgentPanel.tsx", {
997
+ const source = readSource("src/app/chat/AgentPanel.tsx", {
961
998
  encoding: "utf8",
962
999
  });
963
1000
  const overflowMenu = source.slice(
@@ -972,7 +1009,7 @@ describe("AgentPanel header overflow actions", () => {
972
1009
  });
973
1010
 
974
1011
  it("offers sharing from the sidebar overflow for an active chat", () => {
975
- const source = readFileSync("src/app/chat/AgentPanel.tsx", {
1012
+ const source = readSource("src/app/chat/AgentPanel.tsx", {
976
1013
  encoding: "utf8",
977
1014
  });
978
1015
  const overflowMenu = source.slice(
@@ -992,7 +1029,7 @@ describe("AgentPanel header overflow actions", () => {
992
1029
  });
993
1030
 
994
1031
  it("keeps chat headers persistent while switching app surfaces", () => {
995
- const source = readFileSync("src/app/chat/AgentPanel.tsx", {
1032
+ const source = readSource("src/app/chat/AgentPanel.tsx", {
996
1033
  encoding: "utf8",
997
1034
  });
998
1035
 
@@ -1002,10 +1039,10 @@ describe("AgentPanel header overflow actions", () => {
1002
1039
  });
1003
1040
 
1004
1041
  it("supports a persistent two-state sidebar toggle", () => {
1005
- const source = readFileSync("src/app/chat/AgentSidebar.tsx", {
1042
+ const source = readSource("src/app/chat/AgentSidebar.tsx", {
1006
1043
  encoding: "utf8",
1007
1044
  });
1008
- const panelSource = readFileSync("src/app/chat/AgentPanel.tsx", {
1045
+ const panelSource = readSource("src/app/chat/AgentPanel.tsx", {
1009
1046
  encoding: "utf8",
1010
1047
  });
1011
1048
 
@@ -1021,7 +1058,7 @@ describe("AgentPanel header overflow actions", () => {
1021
1058
  });
1022
1059
 
1023
1060
  it("keeps host CLI tabs mounted while chat is active", () => {
1024
- const source = readFileSync("src/app/chat/AgentPanel.tsx", {
1061
+ const source = readSource("src/app/chat/AgentPanel.tsx", {
1025
1062
  encoding: "utf8",
1026
1063
  });
1027
1064
 
@@ -1035,7 +1072,7 @@ describe("AgentPanel header overflow actions", () => {
1035
1072
  });
1036
1073
 
1037
1074
  it("only shows tabs for the active desktop surface", () => {
1038
- const source = readFileSync("src/app/chat/AgentPanel.tsx", {
1075
+ const source = readSource("src/app/chat/AgentPanel.tsx", {
1039
1076
  encoding: "utf8",
1040
1077
  });
1041
1078
 
@@ -1046,7 +1083,7 @@ describe("AgentPanel header overflow actions", () => {
1046
1083
 
1047
1084
  describe("AgentSidebar wide drawer layout", () => {
1048
1085
  it("can disable the panel without unmounting the app surface", () => {
1049
- const source = readFileSync("src/app/chat/AgentSidebar.tsx", {
1086
+ const source = readSource("src/app/chat/AgentSidebar.tsx", {
1050
1087
  encoding: "utf8",
1051
1088
  });
1052
1089
 
@@ -1058,7 +1095,7 @@ describe("AgentSidebar wide drawer layout", () => {
1058
1095
  });
1059
1096
 
1060
1097
  it("does not reserve the drawer placeholder after the panel closes", () => {
1061
- const source = readFileSync("src/app/chat/AgentSidebar.tsx", {
1098
+ const source = readSource("src/app/chat/AgentSidebar.tsx", {
1062
1099
  encoding: "utf8",
1063
1100
  });
1064
1101
  const placeholderStart = source.indexOf("const drawerPlaceholder");
@@ -1083,7 +1120,7 @@ describe("AgentChatSurface chrome defaults", () => {
1083
1120
  });
1084
1121
 
1085
1122
  it("keeps settings out of every chat surface", () => {
1086
- const source = readFileSync("src/app/chat/AgentPanel.tsx", {
1123
+ const source = readSource("src/app/chat/AgentPanel.tsx", {
1087
1124
  encoding: "utf8",
1088
1125
  });
1089
1126
 
@@ -1117,7 +1154,7 @@ describe("AgentChatSurface chrome defaults", () => {
1117
1154
 
1118
1155
  describe("AgentPanel stale lazy chunk recovery", () => {
1119
1156
  it("uses the guarded reload path before the panel reset fallback", () => {
1120
- const source = readFileSync("src/app/chat/AgentPanel.tsx", {
1157
+ const source = readSource("src/app/chat/AgentPanel.tsx", {
1121
1158
  encoding: "utf8",
1122
1159
  });
1123
1160
  const componentDidCatch = source.slice(
@@ -1331,6 +1331,8 @@ export function focusAgentChat() {
1331
1331
 
1332
1332
  function focusAgentChatComposer() {
1333
1333
  const focusComposer = (attempt = 0) => {
1334
+ if (typeof window === "undefined" || typeof document === "undefined")
1335
+ return;
1334
1336
  const panel = document.querySelector(
1335
1337
  ".agent-sidebar-panel[data-agent-sidebar-state='open'], " +
1336
1338
  ".agent-frame-sidebar[data-agent-frame-sidebar-state='open']",
@@ -8,8 +8,8 @@ import {
8
8
  import { IconCloudUpload } from "@tabler/icons-react";
9
9
  import { useRef, type RefObject } from "react";
10
10
 
11
- import { BuilderConnectPopover } from "../settings/index.js";
12
- import { BuilderConnectCard } from "../setup-connections/index.js";
11
+ import { DeferredBuilderConnectPopover as BuilderConnectPopover } from "../settings/deferred-builder-connect-popover.js";
12
+ import { BuilderConnectCard } from "../setup-connections/BuilderConnectCard.js";
13
13
 
14
14
  type FileStorageSetupPopoverCommonProps = {
15
15
  open: boolean;
@@ -1,6 +1,8 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
 
3
- import MultiTabSource from "./MultiTabAssistantChat.tsx?raw";
3
+ import rawMultiTabSource from "./MultiTabAssistantChat.tsx?raw";
4
+
5
+ const MultiTabSource = rawMultiTabSource.replace(/\r\n/g, "\n");
4
6
 
5
7
  describe("MultiTabAssistantChat message count", () => {
6
8
  it("forwards the host callback instead of only counting tabs", () => {
@@ -210,9 +210,11 @@ const modelCatalogMocks = vi.hoisted(() => ({
210
210
  load: null as null | (() => Promise<unknown>),
211
211
  }));
212
212
 
213
- vi.mock("@agent-native/core/client/hooks", async (importOriginal) => {
213
+ vi.mock("@agent-native/core/client/use-action", async (importOriginal) => {
214
214
  const actual =
215
- await importOriginal<typeof import("@agent-native/core/client/hooks")>();
215
+ await importOriginal<
216
+ typeof import("@agent-native/core/client/use-action")
217
+ >();
216
218
  return { ...actual, ...actionMocks };
217
219
  });
218
220
 
@@ -433,6 +435,8 @@ describe("MultiTabAssistantChat postMessage bridge", () => {
433
435
  beforeEach(async () => {
434
436
  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
435
437
  resetThreadMocks();
438
+ actionMocks.callAction.mockReset();
439
+ actionMocks.callAction.mockResolvedValue(null as never);
436
440
  ensureLocalStorage();
437
441
  vi.stubGlobal(
438
442
  "fetch",
@@ -3345,7 +3349,7 @@ describe("MultiTabAssistantChat page overlay", () => {
3345
3349
  ).not.toBeNull();
3346
3350
  });
3347
3351
 
3348
- it("reserves the page top bar even when its actions are temporarily empty", async () => {
3352
+ it("reserves one page-header height when its actions are temporarily empty", async () => {
3349
3353
  await act(async () => {
3350
3354
  root.render(
3351
3355
  <MultiTabAssistantChat
@@ -3359,7 +3363,7 @@ describe("MultiTabAssistantChat page overlay", () => {
3359
3363
  "[data-agent-page-chat-topbar]",
3360
3364
  );
3361
3365
  expect(topbar).not.toBeNull();
3362
- expect(topbar?.className).toContain("pt-14");
3366
+ expect(topbar?.className).toContain("pt-12");
3363
3367
  });
3364
3368
  });
3365
3369