@agent-native/toolkit 0.198.3 → 0.198.5

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 (157) hide show
  1. package/dist/app/agentkit/react/components.d.ts +8 -3
  2. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  3. package/dist/app/agentkit/react/components.js +24 -10
  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 +65 -33
  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/composer/PromptComposer.d.ts +3 -0
  87. package/dist/composer/PromptComposer.d.ts.map +1 -1
  88. package/dist/composer/PromptComposer.js +2 -2
  89. package/dist/composer/PromptComposer.js.map +1 -1
  90. package/dist/composer/TiptapComposer.d.ts +4 -1
  91. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  92. package/dist/composer/TiptapComposer.js +8 -4
  93. package/dist/composer/TiptapComposer.js.map +1 -1
  94. package/dist/icons/ResourceIconPicker.d.ts +6 -1
  95. package/dist/icons/ResourceIconPicker.d.ts.map +1 -1
  96. package/dist/icons/ResourceIconPicker.js +45 -6
  97. package/dist/icons/ResourceIconPicker.js.map +1 -1
  98. package/package.json +6 -4
  99. package/src/app/agentkit/react/components.interaction.spec.tsx +93 -0
  100. package/src/app/agentkit/react/components.spec.tsx +4 -5
  101. package/src/app/agentkit/react/components.tsx +51 -8
  102. package/src/app/agentkit/react/composer-context.spec.tsx +7 -0
  103. package/src/app/agentkit/react/lifecycle.spec.tsx +42 -0
  104. package/src/app/chat/AgentKitAssistantChat.spec.tsx +151 -11
  105. package/src/app/chat/AgentKitAssistantChat.tsx +110 -28
  106. package/src/app/chat/AgentPanel.header.spec.ts +59 -22
  107. package/src/app/chat/AgentSidebar.tsx +2 -0
  108. package/src/app/chat/FileStorageSetupPopover.tsx +2 -2
  109. package/src/app/chat/MultiTabAssistantChat.messageCount.spec.tsx +3 -1
  110. package/src/app/chat/MultiTabAssistantChat.spec.tsx +8 -4
  111. package/src/app/chat/MultiTabAssistantChat.tsx +1 -1
  112. package/src/app/chat/agentkit-chat/action-widget.spec.tsx +5 -17
  113. package/src/app/chat/agentkit-chat/connections.spec.tsx +137 -0
  114. package/src/app/chat/agentkit-chat/connections.ts +2 -37
  115. package/src/app/chat/agentkit-chat/history.tsx +23 -8
  116. package/src/app/chat/chat/history-types.ts +1 -1
  117. package/src/app/chat/chat/tool-call-display.spec.tsx +2 -2
  118. package/src/app/chat/chat/widgets/record-change-widget.spec.tsx +20 -29
  119. package/src/app/chat/chat-first/copy.ts +1 -133
  120. package/src/app/chat/chat-first-copy.ts +133 -0
  121. package/src/app/chat/composer/use-agentkit-capabilities.hardening.spec.tsx +3 -2
  122. package/src/app/feature-flags/FeatureFlagsPanel.tsx +1 -0
  123. package/src/app/i18n/catalogs/ar-SA.ts +2 -1
  124. package/src/app/i18n/catalogs/de-DE.ts +3 -1
  125. package/src/app/i18n/catalogs/en-US.ts +2 -1
  126. package/src/app/i18n/catalogs/es-ES.ts +2 -1
  127. package/src/app/i18n/catalogs/fr-FR.ts +3 -1
  128. package/src/app/i18n/catalogs/hi-IN.ts +2 -1
  129. package/src/app/i18n/catalogs/ja-JP.ts +2 -1
  130. package/src/app/i18n/catalogs/ko-KR.ts +2 -1
  131. package/src/app/i18n/catalogs/pt-BR.ts +3 -1
  132. package/src/app/i18n/catalogs/zh-CN.ts +2 -1
  133. package/src/app/i18n/catalogs/zh-TW.ts +2 -1
  134. package/src/app/integrations/IntegrationDetailPage.spec.tsx +1 -1
  135. package/src/app/labs/LabsSettings.tsx +46 -10
  136. package/src/app/observability/ThumbsFeedback.spec.tsx +15 -0
  137. package/src/app/onboarding/FirstRunOnboarding.spec.tsx +4 -0
  138. package/src/app/org/OrgGeneralSection.tsx +32 -22
  139. package/src/app/org/OrgSwitcher.tsx +3 -2
  140. package/src/app/org/workspace-app-links.spec.ts +4 -0
  141. package/src/app/org/workspace-app-links.ts +8 -1
  142. package/src/app/resources/McpAgentKitConnectionRequest.spec.tsx +371 -2
  143. package/src/app/resources/McpAgentKitConnectionRequest.tsx +171 -11
  144. package/src/app/settings/SettingsTabsPage.spec.tsx +4 -2
  145. package/src/app/settings/shell/SettingsShell.spec.tsx +14 -8
  146. package/src/app/settings/shell/pages/app-group-pages.spec.tsx +35 -5
  147. package/src/app/settings/shell/pages/resource-pages.spec.tsx +3 -0
  148. package/src/app/settings/usage/UsagePage.spec.tsx +3 -3
  149. package/src/app/shared/lazy-chunk-error-boundary.spec.tsx +2 -0
  150. package/src/app-shell/sidebar-overflow.spec.tsx +4 -1
  151. package/src/composer/PromptComposer.tsx +7 -0
  152. package/src/composer/TiptapComposer.spec.ts +26 -4
  153. package/src/composer/TiptapComposer.tsx +12 -3
  154. package/src/design-tweaks/visual-style-controls.spec.tsx +38 -0
  155. package/src/editor/useCollabReconcile.concurrent.spec.ts +2 -1
  156. package/src/icons/ResourceIconPicker.spec.tsx +270 -0
  157. package/src/icons/ResourceIconPicker.tsx +72 -4
@@ -21,6 +21,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
21
21
  import { AgentChat } from "./chat.js";
22
22
  import {
23
23
  AgentActivityGroup,
24
+ AgentConnectionRequestCard,
24
25
  AgentKitChat,
25
26
  AgentMessageActions,
26
27
  formatAgentKitDuration,
@@ -3160,6 +3161,47 @@ describe("AgentKit subscriptions and recovery", () => {
3160
3161
  });
3161
3162
  });
3162
3163
 
3164
+ it("leaves a workspace request pending while its connection callback redirects", async () => {
3165
+ const threadId = "thread-workspace-connection";
3166
+ const store = observableController({
3167
+ connection: "connected",
3168
+ capabilities: { connectionRequests: true },
3169
+ capabilitiesStatus: "ready",
3170
+ threads: { [threadId]: createAgentThreadState(threadId) },
3171
+ revision: 1,
3172
+ });
3173
+ const request = {
3174
+ id: "connection-google-drive",
3175
+ provider: "google_drive",
3176
+ reason: "connect" as const,
3177
+ status: "requested" as const,
3178
+ };
3179
+ const onConnect = vi.fn(async () => undefined);
3180
+ const tree = mount();
3181
+
3182
+ await tree.render(
3183
+ <AgentKitProvider controller={store.controller} threadId={threadId}>
3184
+ <AgentConnectionRequestCard
3185
+ request={request}
3186
+ runId="run-1"
3187
+ providerLabel="Google Drive"
3188
+ onConnect={onConnect}
3189
+ />
3190
+ </AgentKitProvider>,
3191
+ );
3192
+ const button = Array.from(tree.container.querySelectorAll("button")).find(
3193
+ (candidate) => candidate.textContent === "Connect",
3194
+ );
3195
+ await act(async () => {
3196
+ button?.click();
3197
+ await Promise.resolve();
3198
+ });
3199
+
3200
+ expect(tree.container.textContent).toContain("Connect Google Drive");
3201
+ expect(onConnect).toHaveBeenCalledWith(request);
3202
+ expect(store.controller.resolveConnectionRequest).not.toHaveBeenCalled();
3203
+ });
3204
+
3163
3205
  it("renders a pre-connection-request controller projection", async () => {
3164
3206
  const legacyThread = createAgentThreadState("thread-legacy") as ReturnType<
3165
3207
  typeof createAgentThreadState
@@ -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,11 +794,29 @@ 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);
799
+ expect(chatMocks.composerProps.announcePendingSubmission).toBe(false);
762
800
  expect(container.querySelector('[role="status"]')?.textContent).toBe(
763
801
  "agentChat.status.thinking",
764
802
  );
765
803
  });
766
804
 
805
+ it("keeps the pending announcement when the transcript has no visible user message", async () => {
806
+ chatMocks.history = { isSubmissionInFlight: true };
807
+ chatMocks.thread.messages = [
808
+ {
809
+ id: "assistant-last",
810
+ role: "assistant",
811
+ parts: [{ type: "text", text: "Earlier response" }],
812
+ },
813
+ ];
814
+ await mount(baseProps());
815
+
816
+ expect(chatMocks.composerProps.announcePendingSubmission).toBe(true);
817
+ expect(container.querySelector('[role="status"]')).toBeNull();
818
+ });
819
+
767
820
  it("gives JS callers a migration error for the removed createAdapter prop", async () => {
768
821
  const errors: Error[] = [];
769
822
  container = document.createElement("div");
@@ -847,6 +900,29 @@ describe("AgentKitAssistantChat host behavior", () => {
847
900
  return client;
848
901
  }
849
902
 
903
+ it("keeps the real editor editable while a submission is in flight", async () => {
904
+ chatMocks.useRealChat = true;
905
+ chatMocks.history = { isSubmissionInFlight: true };
906
+ await useRealComposer();
907
+ await mount(baseProps({ showModelSelector: false }));
908
+
909
+ const editor = container.querySelector<HTMLElement>(
910
+ '[contenteditable="true"]',
911
+ );
912
+ expect(editor).not.toBeNull();
913
+ expect(chatMocks.composerProps.submissionDisabled).toBe(true);
914
+
915
+ await act(async () =>
916
+ chatMocks.composerProps.composerRef.current.setText("Keep this draft"),
917
+ );
918
+ expect(editor?.textContent).toBe("Keep this draft");
919
+ expect(
920
+ container.querySelector<HTMLButtonElement>(
921
+ '[data-agent-composer-slot="send-button"]',
922
+ )?.disabled,
923
+ ).toBe(true);
924
+ });
925
+
850
926
  it.each(["default", "compact"] as const)(
851
927
  "clears the real %s composer before startRun resolves and preserves the next draft",
852
928
  async (composerLayoutVariant) => {
@@ -855,12 +931,13 @@ describe("AgentKitAssistantChat host behavior", () => {
855
931
  chatMocks.useRealChat = true;
856
932
  const client = await useRealComposer(startRun);
857
933
  try {
858
- await mount(
859
- baseProps({
860
- composerLayoutVariant,
861
- showModelSelector: false,
862
- }),
863
- );
934
+ const props = baseProps({
935
+ composerLayoutVariant,
936
+ showModelSelector: false,
937
+ });
938
+ const openThread = vi.spyOn(client, "openThread");
939
+ chatMocks.useRealRoot = true;
940
+ await mount(props);
864
941
  container.style.width =
865
942
  composerLayoutVariant === "compact" ? "320px" : "960px";
866
943
  const composer = chatMocks.composerProps.composerRef.current;
@@ -884,6 +961,18 @@ describe("AgentKitAssistantChat host behavior", () => {
884
961
  expect(client.getThread(chatMocks.threadId).activeRunIds).toEqual([]);
885
962
  expect(editor.textContent).toBe("");
886
963
 
964
+ await act(async () =>
965
+ root.render(<AgentKitAssistantChat {...props} isNewThread={false} />),
966
+ );
967
+ await flush();
968
+
969
+ expect(chatMocks.rootProps.load).toBe("manual");
970
+ expect(openThread).not.toHaveBeenCalled();
971
+ expect(
972
+ container
973
+ .querySelector<HTMLElement>("[contenteditable]")
974
+ ?.getAttribute("contenteditable"),
975
+ ).toBe("true");
887
976
  await act(async () => composer.setText("Next draft"));
888
977
  await act(async () => started.resolve({ runId: "run-latency" }));
889
978
 
@@ -933,6 +1022,28 @@ describe("AgentKitAssistantChat host behavior", () => {
933
1022
  },
934
1023
  );
935
1024
 
1025
+ it("disables host suggestions while async submission is pending", async () => {
1026
+ const suggestion = seedFollowup();
1027
+ await mount(baseProps());
1028
+ const chips = () =>
1029
+ [...container.querySelectorAll("button")].filter(
1030
+ (button) => button.textContent === suggestion.label,
1031
+ );
1032
+ expect(chips().length).toBeGreaterThan(0);
1033
+ expect(chips().some((button) => button.disabled)).toBe(false);
1034
+
1035
+ await act(async () => {
1036
+ chatMocks.composerProps.onSubmissionPendingChange(true);
1037
+ });
1038
+
1039
+ expect(chips().every((button) => button.disabled)).toBe(true);
1040
+
1041
+ await act(async () => {
1042
+ chatMocks.composerProps.onSubmissionPendingChange(false);
1043
+ });
1044
+ expect(chips().every((button) => !button.disabled)).toBe(true);
1045
+ });
1046
+
936
1047
  it.each([undefined, "context-chips", "after-composer", "hidden"] as const)(
937
1048
  "preserves initial starters with the real composer (placement: %s)",
938
1049
  async (suggestionPlacement) => {
@@ -1585,7 +1696,7 @@ describe("AgentKitAssistantChat host behavior", () => {
1585
1696
  expect(chatMocks.appState.has(stateKey!)).toBe(false);
1586
1697
  });
1587
1698
 
1588
- it("keeps a queued follow-up queued if its run finishes during provider discovery", async () => {
1699
+ it("preserves queued intent when its run finishes during provider discovery", async () => {
1589
1700
  chatMocks.readiness = {
1590
1701
  canChat: false,
1591
1702
  missing: false,
@@ -1611,12 +1722,27 @@ describe("AgentKitAssistantChat host behavior", () => {
1611
1722
  [],
1612
1723
  [],
1613
1724
  { intent: "queued" },
1725
+ async () => ({}),
1614
1726
  );
1615
1727
  });
1616
1728
 
1617
1729
  expect(chatMocks.control.sendMessage).not.toHaveBeenCalled();
1618
1730
  expect(chatMocks.control.queueMessage).not.toHaveBeenCalled();
1619
1731
 
1732
+ const stateKey = [...chatMocks.appState.keys()].find((key) =>
1733
+ key.startsWith("agentkit-deferred-provider-submissions:"),
1734
+ );
1735
+ expect(chatMocks.appState.get(stateKey!)).toMatchObject({
1736
+ submissions: [
1737
+ {
1738
+ composerOptions: {
1739
+ intent: "queued",
1740
+ queuedWhileRunActive: true,
1741
+ },
1742
+ },
1743
+ ],
1744
+ });
1745
+
1620
1746
  chatMocks.thread.activeRunIds = [];
1621
1747
  chatMocks.thread.runs["analytics-run"].status = "completed";
1622
1748
  chatMocks.readiness = {
@@ -1640,6 +1766,7 @@ describe("AgentKitAssistantChat host behavior", () => {
1640
1766
  }),
1641
1767
  );
1642
1768
  expect(chatMocks.control.sendMessage).not.toHaveBeenCalled();
1769
+ expect(chatMocks.appState.has(stateKey!)).toBe(false);
1643
1770
  });
1644
1771
 
1645
1772
  it("keeps the draft rejected if provider status becomes missing before submit", async () => {
@@ -3338,11 +3465,11 @@ describe("AgentKitAssistantChat host behavior", () => {
3338
3465
  chatMocks.thread.activeRunIds = ["run-1"];
3339
3466
  await mount(baseProps());
3340
3467
 
3341
- await act(async () => {
3342
- await chatMocks.resumeProps.onMessageResume({
3343
- message: "Continue after connecting the integration.",
3344
- });
3468
+ const resume = chatMocks.resumeProps.onMessageResume({
3469
+ message: "Continue after connecting the integration.",
3345
3470
  });
3471
+ expect(resume).toBeInstanceOf(Promise);
3472
+ await act(async () => resume);
3346
3473
  expect(chatMocks.control.queueMessage).toHaveBeenCalledWith(
3347
3474
  expect.objectContaining({
3348
3475
  text: "Continue after connecting the integration.",
@@ -3382,6 +3509,19 @@ describe("AgentKitAssistantChat host behavior", () => {
3382
3509
  expect(chatMocks.control.sendMessage).not.toHaveBeenCalled();
3383
3510
  });
3384
3511
 
3512
+ it("keeps failed integration prompt submissions resumable", async () => {
3513
+ chatMocks.thread.activeRunIds = ["run-1"];
3514
+ const submissionError = new Error("Temporary send failure");
3515
+ chatMocks.control.queueMessage.mockRejectedValueOnce(submissionError);
3516
+ await mount(baseProps());
3517
+
3518
+ await expect(
3519
+ chatMocks.resumeProps.onMessageResume({
3520
+ message: "Continue after OAuth.",
3521
+ }),
3522
+ ).rejects.toBe(submissionError);
3523
+ });
3524
+
3385
3525
  it("shows the missing-final-response warning from recovered run metadata", async () => {
3386
3526
  chatMocks.thread.messages = [
3387
3527
  {
@@ -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,
@@ -502,6 +511,9 @@ interface AgentKitSurfaceContextValue {
502
511
  setupBouncePulse: number;
503
512
  bounceSetupCard: () => void;
504
513
  isSubmissionInFlight: boolean;
514
+ composerSubmissionPending: boolean;
515
+ onComposerSubmissionPendingChange: (pending: boolean) => void;
516
+ isThinkingVisibleInTranscript: boolean;
505
517
  contextItems: AgentChatContextItem[];
506
518
  suggestions: AgentSuggestionInput[];
507
519
  showSuggestions: boolean;
@@ -594,8 +606,13 @@ export const AgentKitAssistantChat = forwardRef<
594
606
  : readAgentKitThreadHandoffSnapshot(
595
607
  createAgentKitThreadHandoffKey(props, threadId),
596
608
  );
609
+ const [manualLoadThreadId, setManualLoadThreadId] = useState<string | null>(
610
+ () => (props.isNewThread ? threadId : null),
611
+ );
612
+ const hasManualLoadForCurrentThread =
613
+ props.isNewThread || manualLoadThreadId === threadId;
597
614
  const [threadRestore, setThreadRestore] = useState<ThreadRestoreState>(() =>
598
- props.isThreadStateLoading || !props.isNewThread
615
+ props.isThreadStateLoading || !hasManualLoadForCurrentThread
599
616
  ? { status: "loading" }
600
617
  : { status: "ready" },
601
618
  );
@@ -611,15 +628,25 @@ export const AgentKitAssistantChat = forwardRef<
611
628
  setRestoreRetryLoadPhase("idle");
612
629
  setRestoreRetryThreadId(null);
613
630
  }, []);
631
+ useEffect(() => {
632
+ setManualLoadThreadId((current) =>
633
+ props.isNewThread ? threadId : current === threadId ? current : null,
634
+ );
635
+ }, [props.isNewThread, threadId]);
614
636
  useEffect(() => {
615
637
  setThreadRestore(
616
- props.isThreadStateLoading || !props.isNewThread
638
+ props.isThreadStateLoading || !hasManualLoadForCurrentThread
617
639
  ? { status: "loading" }
618
640
  : { status: "ready" },
619
641
  );
620
642
  setRestoreRetryLoadPhase("idle");
621
643
  setRestoreRetryThreadId(null);
622
- }, [props.isNewThread, props.isThreadStateLoading, threadId]);
644
+ }, [
645
+ hasManualLoadForCurrentThread,
646
+ props.isNewThread,
647
+ props.isThreadStateLoading,
648
+ threadId,
649
+ ]);
623
650
  const onThreadRestoreLoadError = useCallback(
624
651
  (error: unknown) => {
625
652
  const record = asRecord(error);
@@ -989,7 +1016,7 @@ export const AgentKitAssistantChat = forwardRef<
989
1016
  ]);
990
1017
  const agentKitLoad =
991
1018
  props.isThreadStateLoading ||
992
- props.isNewThread ||
1019
+ hasManualLoadForCurrentThread ||
993
1020
  (restoreRetryThreadId === threadId && restoreRetryLoadPhase === "release")
994
1021
  ? "manual"
995
1022
  : "auto";
@@ -1125,6 +1152,8 @@ const AgentKitAssistantChatBody = forwardRef<
1125
1152
  const isRestoring =
1126
1153
  history?.isRestoring === true || props.threadRestore.status === "loading";
1127
1154
  const isSubmissionInFlight = history?.isSubmissionInFlight === true;
1155
+ const [composerSubmissionPending, setComposerSubmissionPending] =
1156
+ useState(false);
1128
1157
  const [authError, setAuthError] = useState<{
1129
1158
  sessionExpired?: boolean;
1130
1159
  } | null>(null);
@@ -1271,6 +1300,11 @@ const AgentKitAssistantChatBody = forwardRef<
1271
1300
  [thread.messages],
1272
1301
  );
1273
1302
  const isRunning = hasActiveAgentRuns(thread);
1303
+ const lastMessage = thread.messages.at(-1);
1304
+ const isThinkingVisibleInTranscript =
1305
+ (isRunning || isSubmissionInFlight) &&
1306
+ lastMessage?.role === "user" &&
1307
+ lastMessage.metadata?.hideUserMessage !== true;
1274
1308
  const isThreadRunning = useCallback(
1275
1309
  () => hasActiveAgentRuns(controller.getThread(threadId)),
1276
1310
  [controller, threadId],
@@ -2204,7 +2238,7 @@ const AgentKitAssistantChatBody = forwardRef<
2204
2238
  try {
2205
2239
  const preparedOptions = prepare ? await prepare() : composerOptions;
2206
2240
  const submittedOptions = captureQueuedRunState(
2207
- preparedOptions,
2241
+ preserveQueuedIntent(preparedOptions, composerOptions),
2208
2242
  runWasActiveAtSubmit,
2209
2243
  );
2210
2244
  const result = await submit(
@@ -2231,7 +2265,10 @@ const AgentKitAssistantChatBody = forwardRef<
2231
2265
  text,
2232
2266
  files,
2233
2267
  references,
2234
- captureQueuedRunState(preparedOptions, runWasActiveAtSubmit),
2268
+ captureQueuedRunState(
2269
+ preserveQueuedIntent(preparedOptions, composerOptions),
2270
+ runWasActiveAtSubmit,
2271
+ ),
2235
2272
  );
2236
2273
  } catch (error) {
2237
2274
  dispatchSetupRequiredEvent(error, props.tabId, threadId);
@@ -2552,23 +2589,32 @@ const AgentKitAssistantChatBody = forwardRef<
2552
2589
  [isThreadRunning, submit],
2553
2590
  );
2554
2591
  const resumeIntegrationPrompt = useCallback(
2555
- (message: string) => {
2556
- if (props.isActiveComposer === false) return;
2557
- void send(message).catch((error) => {
2592
+ async (message: string) => {
2593
+ if (props.isActiveComposer === false) {
2594
+ throw new Error("Cannot resume a request in an inactive chat.");
2595
+ }
2596
+ try {
2597
+ const result = await send(message);
2598
+ if (result.status === "rejected") {
2599
+ throw new Error(`Chat resume was rejected: ${result.reason}.`);
2600
+ }
2601
+ } catch (error) {
2558
2602
  dispatchSetupRequiredEvent(error, props.tabId, threadId);
2559
- });
2603
+ throw error;
2604
+ }
2560
2605
  },
2561
2606
  [props.isActiveComposer, props.tabId, send, threadId],
2562
2607
  );
2563
2608
  const submitSuggestion = useCallback(
2564
2609
  (suggestion: AgentSuggestionInput) => {
2610
+ if (composerSubmissionPending) return;
2565
2611
  const handler = suggestionSubmitRef.current;
2566
2612
  if (!handler || handler.threadId !== threadId) return;
2567
2613
  void handler.submit(suggestion).catch((error) => {
2568
2614
  dispatchSetupRequiredEvent(error, props.tabId, threadId);
2569
2615
  });
2570
2616
  },
2571
- [props.tabId, threadId],
2617
+ [composerSubmissionPending, props.tabId, threadId],
2572
2618
  );
2573
2619
  const retryDeferredSubmission = useCallback(async () => {
2574
2620
  if (!deferredProviderSubmissionFailureId) return;
@@ -2748,6 +2794,9 @@ const AgentKitAssistantChatBody = forwardRef<
2748
2794
  setupBouncePulse,
2749
2795
  bounceSetupCard,
2750
2796
  isSubmissionInFlight,
2797
+ composerSubmissionPending,
2798
+ onComposerSubmissionPendingChange: setComposerSubmissionPending,
2799
+ isThinkingVisibleInTranscript,
2751
2800
  contextItems,
2752
2801
  voiceTranscriptMessages,
2753
2802
  selectionLength,
@@ -2775,15 +2824,19 @@ const AgentKitAssistantChatBody = forwardRef<
2775
2824
  { threadId: targetThreadId, runId, requestId },
2776
2825
  request,
2777
2826
  ) => {
2778
- if (targetThreadId !== threadId) return;
2827
+ if (targetThreadId !== threadId) {
2828
+ throw new Error(
2829
+ "Cannot resume a connection request in another chat.",
2830
+ );
2831
+ }
2779
2832
  return control.resolveConnectionRequest(runId, requestId, {
2780
2833
  status: "connected",
2781
2834
  message: request.message,
2782
2835
  });
2783
2836
  }}
2784
- onMessageResume={(request) => {
2785
- resumeIntegrationPrompt(request.message);
2786
- }}
2837
+ onMessageResume={(request) =>
2838
+ resumeIntegrationPrompt(request.message)
2839
+ }
2787
2840
  />
2788
2841
  )}
2789
2842
  <RunStuckBanner
@@ -2805,6 +2858,24 @@ const AgentKitAssistantChatBody = forwardRef<
2805
2858
  void sendRecoveryMessage(RECOVERY_CONTINUE_PROMPT, "continue")
2806
2859
  }
2807
2860
  />
2861
+ {history?.historyLoadFailed ? (
2862
+ <div
2863
+ role="alert"
2864
+ className="mx-3 mb-2 flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2 text-xs"
2865
+ >
2866
+ <span className="text-muted-foreground">
2867
+ {t("agentChat.message.historyUnavailable")}
2868
+ </span>
2869
+ <button
2870
+ type="button"
2871
+ onClick={history.retryHistory}
2872
+ disabled={history.isRetryingHistory}
2873
+ className="shrink-0 font-medium text-foreground hover:underline disabled:cursor-not-allowed disabled:opacity-50"
2874
+ >
2875
+ {t("agentChat.common.retry")}
2876
+ </button>
2877
+ </div>
2878
+ ) : null}
2808
2879
  <AgentKitChat
2809
2880
  className={props.className}
2810
2881
  composerProps={{ attachmentsEnabled: fileStorageConfigured }}
@@ -2910,7 +2981,8 @@ function AgentKitEmptyState({ threadId }: { threadId: string }) {
2910
2981
  disabled={
2911
2982
  !surface.canChat ||
2912
2983
  surface.props.composerDisabled ||
2913
- surface.props.composerSubmissionDisabled
2984
+ surface.props.composerSubmissionDisabled ||
2985
+ surface.composerSubmissionPending
2914
2986
  }
2915
2987
  onClick={() => surface.submitSuggestion(suggestion)}
2916
2988
  className="w-full rounded-xl border border-border/70 bg-card/60 px-3 py-2.5 text-left text-[13px] text-muted-foreground shadow-sm transition-colors hover:border-border hover:bg-card hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
@@ -3008,11 +3080,6 @@ function AgentKitTranscript({ children, threadId }: AgentKitRegionRenderProps) {
3008
3080
  const threadMessageIds = new Set(
3009
3081
  thread.messages.map((message) => message.id),
3010
3082
  );
3011
- const lastMessage = thread.messages.at(-1);
3012
- const showThinking =
3013
- (surface.isRunning || surface.isSubmissionInFlight) &&
3014
- lastMessage?.role === "user" &&
3015
- lastMessage.metadata?.hideUserMessage !== true;
3016
3083
  const pendingVoiceMessages = surface.voiceTranscriptMessages.filter(
3017
3084
  (message) => !threadMessageIds.has(message.id),
3018
3085
  );
@@ -3033,7 +3100,8 @@ function AgentKitTranscript({ children, threadId }: AgentKitRegionRenderProps) {
3033
3100
  !surface.canChat ||
3034
3101
  surface.props.composerDisabled ||
3035
3102
  surface.props.composerSubmissionDisabled ||
3036
- surface.isSubmissionInFlight
3103
+ surface.isSubmissionInFlight ||
3104
+ surface.composerSubmissionPending
3037
3105
  }
3038
3106
  onSelect={surface.submitSuggestion}
3039
3107
  className="agentkit-host-suggestions"
@@ -3189,7 +3257,7 @@ function AgentKitTranscript({ children, threadId }: AgentKitRegionRenderProps) {
3189
3257
  threadId={threadId}
3190
3258
  />
3191
3259
  ))}
3192
- {showThinking ? (
3260
+ {surface.isThinkingVisibleInTranscript ? (
3193
3261
  <div
3194
3262
  role="status"
3195
3263
  aria-live="polite"
@@ -3338,6 +3406,9 @@ function AgentKitComposerSurface({
3338
3406
  isRunning,
3339
3407
  isRestoring,
3340
3408
  isSubmissionInFlight,
3409
+ composerSubmissionPending,
3410
+ onComposerSubmissionPendingChange,
3411
+ isThinkingVisibleInTranscript,
3341
3412
  hasRenderedMessages,
3342
3413
  threadRestore,
3343
3414
  suggestions,
@@ -3373,6 +3444,9 @@ function AgentKitComposerSurface({
3373
3444
  isRunning: boolean;
3374
3445
  isRestoring: boolean;
3375
3446
  isSubmissionInFlight: boolean;
3447
+ composerSubmissionPending: boolean;
3448
+ onComposerSubmissionPendingChange: (pending: boolean) => void;
3449
+ isThinkingVisibleInTranscript: boolean;
3376
3450
  hasRenderedMessages: boolean;
3377
3451
  setupBouncePulse: number;
3378
3452
  bounceSetupCard: () => void;
@@ -3618,7 +3692,8 @@ function AgentKitComposerSurface({
3618
3692
  !canChat ||
3619
3693
  props.composerDisabled ||
3620
3694
  props.composerSubmissionDisabled ||
3621
- isSubmissionInFlight
3695
+ isSubmissionInFlight ||
3696
+ composerSubmissionPending
3622
3697
  }
3623
3698
  onSelect={submitSuggestion}
3624
3699
  className="agentkit-home-suggestions"
@@ -3668,12 +3743,12 @@ function AgentKitComposerSurface({
3668
3743
  disabled={
3669
3744
  (!canChat && !providerSubmissionPending) ||
3670
3745
  props.composerDisabled ||
3671
- isRestoring ||
3672
- isSubmissionInFlight
3746
+ isRestoring
3673
3747
  }
3674
3748
  submissionDisabled={
3675
3749
  (!canChat && !providerSubmissionPending) ||
3676
- props.composerSubmissionDisabled === true
3750
+ props.composerSubmissionDisabled === true ||
3751
+ isSubmissionInFlight
3677
3752
  }
3678
3753
  onDisabledClick={
3679
3754
  props.composerDisabled || !setupMissing
@@ -3692,6 +3767,8 @@ function AgentKitComposerSurface({
3692
3767
  requireAgentEngine={false}
3693
3768
  onTextChange={onTextChange}
3694
3769
  onBeforeSubmit={onBeforeSubmit}
3770
+ announcePendingSubmission={!isThinkingVisibleInTranscript}
3771
+ onSubmissionPendingChange={onComposerSubmissionPendingChange}
3695
3772
  contextItems={visibleContextItems}
3696
3773
  contextMenuItems={composerContext?.menuItems}
3697
3774
  onRemoveContextItem={(key) => {
@@ -3835,7 +3912,8 @@ function AgentKitComposerSurface({
3835
3912
  !canChat ||
3836
3913
  props.composerDisabled ||
3837
3914
  props.composerSubmissionDisabled ||
3838
- isSubmissionInFlight
3915
+ isSubmissionInFlight ||
3916
+ composerSubmissionPending
3839
3917
  }
3840
3918
  onSelect={submitSuggestion}
3841
3919
  className="agentkit-home-suggestions"
@@ -4128,6 +4206,10 @@ function AgentKitConnectionRequest({
4128
4206
  <McpAgentKitConnectionRequestCard
4129
4207
  provider={value.provider}
4130
4208
  detail={value.detail}
4209
+ reason={value.reason}
4210
+ status={value.status}
4211
+ appId={value.appId}
4212
+ source={value.source}
4131
4213
  target={{ threadId, runId, requestId: value.id }}
4132
4214
  onConnected={() =>
4133
4215
  control.resolveConnectionRequest(runId, value.id, {