@agent-native/toolkit 0.204.0 → 0.204.1-nightly-20261008065100

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 (168) hide show
  1. package/agent-native.eject.json +2 -0
  2. package/dist/app/agent-page/AgentJobsTab.d.ts.map +1 -1
  3. package/dist/app/agent-page/AgentJobsTab.js +4 -2
  4. package/dist/app/agent-page/AgentJobsTab.js.map +1 -1
  5. package/dist/app/agent-page/AutomationDetailsDialog.d.ts +2 -1
  6. package/dist/app/agent-page/AutomationDetailsDialog.d.ts.map +1 -1
  7. package/dist/app/agent-page/AutomationDetailsDialog.js +8 -4
  8. package/dist/app/agent-page/AutomationDetailsDialog.js.map +1 -1
  9. package/dist/app/agent-page/ScheduledTriggerNotice.d.ts.map +1 -1
  10. package/dist/app/agent-page/ScheduledTriggerNotice.js +41 -19
  11. package/dist/app/agent-page/ScheduledTriggerNotice.js.map +1 -1
  12. package/dist/app/agent-page/use-jobs.d.ts +1 -1
  13. package/dist/app/agent-page/use-jobs.d.ts.map +1 -1
  14. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  15. package/dist/app/agentkit/react/components.js +124 -45
  16. package/dist/app/agentkit/react/components.js.map +1 -1
  17. package/dist/app/agentkit/react/context.d.ts +22 -1
  18. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  19. package/dist/app/agentkit/react/context.js +14 -1
  20. package/dist/app/agentkit/react/context.js.map +1 -1
  21. package/dist/app/agentkit/react/root.d.ts +3 -2
  22. package/dist/app/agentkit/react/root.d.ts.map +1 -1
  23. package/dist/app/agentkit/react/root.js +2 -2
  24. package/dist/app/agentkit/react/root.js.map +1 -1
  25. package/dist/app/agentkit/react/styles.css +32 -2
  26. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  27. package/dist/app/chat/AgentKitAssistantChat.js +74 -7
  28. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  29. package/dist/app/chat/AgentPanel.js +1 -1
  30. package/dist/app/chat/AgentPanel.js.map +1 -1
  31. package/dist/app/chat/MultiTabAssistantChat.d.ts.map +1 -1
  32. package/dist/app/chat/MultiTabAssistantChat.js +10 -9
  33. package/dist/app/chat/MultiTabAssistantChat.js.map +1 -1
  34. package/dist/app/chat/RunStuckBanner.d.ts +8 -1
  35. package/dist/app/chat/RunStuckBanner.d.ts.map +1 -1
  36. package/dist/app/chat/RunStuckBanner.js +68 -6
  37. package/dist/app/chat/RunStuckBanner.js.map +1 -1
  38. package/dist/app/chat/chat/run-recovery.d.ts +4 -2
  39. package/dist/app/chat/chat/run-recovery.d.ts.map +1 -1
  40. package/dist/app/chat/chat/run-recovery.js +3 -3
  41. package/dist/app/chat/chat/run-recovery.js.map +1 -1
  42. package/dist/app/chat/chat/surface-types.d.ts +1 -0
  43. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  44. package/dist/app/chat/composer/use-mention-search.d.ts +1 -0
  45. package/dist/app/chat/composer/use-mention-search.d.ts.map +1 -1
  46. package/dist/app/chat/fallback-chat-title.d.ts +2 -0
  47. package/dist/app/chat/fallback-chat-title.d.ts.map +1 -0
  48. package/dist/app/chat/fallback-chat-title.js +11 -0
  49. package/dist/app/chat/fallback-chat-title.js.map +1 -0
  50. package/dist/app/chat/feedback-report.d.ts +12 -0
  51. package/dist/app/chat/feedback-report.d.ts.map +1 -0
  52. package/dist/app/chat/feedback-report.js +21 -0
  53. package/dist/app/chat/feedback-report.js.map +1 -0
  54. package/dist/app/chat/reconcile-settled-run.d.ts +15 -0
  55. package/dist/app/chat/reconcile-settled-run.d.ts.map +1 -0
  56. package/dist/app/chat/reconcile-settled-run.js +41 -0
  57. package/dist/app/chat/reconcile-settled-run.js.map +1 -0
  58. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  59. package/dist/app/i18n/catalogs/ar-SA.js +9 -0
  60. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  61. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  62. package/dist/app/i18n/catalogs/de-DE.js +9 -0
  63. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  64. package/dist/app/i18n/catalogs/en-US.d.ts +9 -0
  65. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  66. package/dist/app/i18n/catalogs/en-US.js +9 -0
  67. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  68. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  69. package/dist/app/i18n/catalogs/es-ES.js +9 -0
  70. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  71. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  72. package/dist/app/i18n/catalogs/fr-FR.js +9 -0
  73. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  74. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  75. package/dist/app/i18n/catalogs/hi-IN.js +9 -0
  76. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  77. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  78. package/dist/app/i18n/catalogs/ja-JP.js +9 -0
  79. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  80. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  81. package/dist/app/i18n/catalogs/ko-KR.js +9 -0
  82. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  83. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  84. package/dist/app/i18n/catalogs/pt-BR.js +9 -0
  85. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  86. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  87. package/dist/app/i18n/catalogs/zh-CN.js +9 -0
  88. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  89. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  90. package/dist/app/i18n/catalogs/zh-TW.js +9 -0
  91. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  92. package/dist/app/providers/AppProviders.d.ts.map +1 -1
  93. package/dist/app/providers/AppProviders.js +7 -1
  94. package/dist/app/providers/AppProviders.js.map +1 -1
  95. package/dist/app/settings/shell/pages/audit.d.ts.map +1 -1
  96. package/dist/app/settings/shell/pages/audit.js +11 -2
  97. package/dist/app/settings/shell/pages/audit.js.map +1 -1
  98. package/dist/app/shared/AppShellSkeleton.d.ts.map +1 -1
  99. package/dist/app/shared/AppShellSkeleton.js +4 -0
  100. package/dist/app/shared/AppShellSkeleton.js.map +1 -1
  101. package/dist/composer/ComposerContextMenu.d.ts +14 -1
  102. package/dist/composer/ComposerContextMenu.d.ts.map +1 -1
  103. package/dist/composer/ComposerContextMenu.js +65 -19
  104. package/dist/composer/ComposerContextMenu.js.map +1 -1
  105. package/dist/composer/MentionPopover.d.ts.map +1 -1
  106. package/dist/composer/MentionPopover.js +3 -3
  107. package/dist/composer/MentionPopover.js.map +1 -1
  108. package/dist/composer/PromptComposer.d.ts +1 -1
  109. package/dist/composer/PromptComposer.d.ts.map +1 -1
  110. package/dist/composer/TiptapComposer.d.ts +2 -2
  111. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  112. package/dist/composer/TiptapComposer.js +92 -36
  113. package/dist/composer/TiptapComposer.js.map +1 -1
  114. package/dist/composer/use-mention-search.d.ts +1 -0
  115. package/dist/composer/use-mention-search.d.ts.map +1 -1
  116. package/dist/composer/use-mention-search.js +8 -1
  117. package/dist/composer/use-mention-search.js.map +1 -1
  118. package/package.json +4 -4
  119. package/src/app/agent-page/AgentJobsTab.blocked.spec.tsx +43 -0
  120. package/src/app/agent-page/AgentJobsTab.tsx +14 -5
  121. package/src/app/agent-page/AutomationDetailsDialog.tsx +22 -8
  122. package/src/app/agent-page/ScheduledTriggerNotice.spec.tsx +45 -3
  123. package/src/app/agent-page/ScheduledTriggerNotice.tsx +52 -31
  124. package/src/app/agent-page/use-jobs.ts +1 -1
  125. package/src/app/agentkit/react/components.interaction.spec.tsx +311 -1
  126. package/src/app/agentkit/react/components.spec.tsx +106 -2
  127. package/src/app/agentkit/react/components.tsx +217 -80
  128. package/src/app/agentkit/react/context.tsx +36 -0
  129. package/src/app/agentkit/react/root.tsx +4 -0
  130. package/src/app/agentkit/react/styles.css +32 -2
  131. package/src/app/chat/AgentKitAssistantChat.spec.tsx +140 -18
  132. package/src/app/chat/AgentKitAssistantChat.tsx +123 -7
  133. package/src/app/chat/AgentPanel.header.spec.ts +43 -7
  134. package/src/app/chat/AgentPanel.tsx +1 -1
  135. package/src/app/chat/MultiTabAssistantChat.spec.tsx +34 -0
  136. package/src/app/chat/MultiTabAssistantChat.tsx +10 -9
  137. package/src/app/chat/RunStuckBanner.spec.tsx +258 -17
  138. package/src/app/chat/RunStuckBanner.tsx +107 -5
  139. package/src/app/chat/chat/run-recovery.tsx +10 -2
  140. package/src/app/chat/chat/surface-types.ts +1 -0
  141. package/src/app/chat/fallback-chat-title.ts +10 -0
  142. package/src/app/chat/feedback-report.spec.ts +45 -0
  143. package/src/app/chat/feedback-report.ts +27 -0
  144. package/src/app/chat/reconcile-settled-run.spec.ts +112 -0
  145. package/src/app/chat/reconcile-settled-run.ts +57 -0
  146. package/src/app/i18n/catalogs/ar-SA.ts +12 -0
  147. package/src/app/i18n/catalogs/de-DE.ts +12 -0
  148. package/src/app/i18n/catalogs/en-US.ts +12 -0
  149. package/src/app/i18n/catalogs/es-ES.ts +12 -0
  150. package/src/app/i18n/catalogs/fr-FR.ts +12 -0
  151. package/src/app/i18n/catalogs/hi-IN.ts +12 -0
  152. package/src/app/i18n/catalogs/ja-JP.ts +12 -0
  153. package/src/app/i18n/catalogs/ko-KR.ts +12 -0
  154. package/src/app/i18n/catalogs/pt-BR.ts +12 -0
  155. package/src/app/i18n/catalogs/zh-CN.ts +10 -0
  156. package/src/app/i18n/catalogs/zh-TW.ts +10 -0
  157. package/src/app/providers/AppProviders.spec.tsx +29 -0
  158. package/src/app/providers/AppProviders.tsx +13 -0
  159. package/src/app/settings/shell/pages/audit.spec.tsx +30 -0
  160. package/src/app/settings/shell/pages/audit.tsx +12 -1
  161. package/src/app/shared/AppShellSkeleton.tsx +4 -0
  162. package/src/composer/ComposerContextMenu.tsx +94 -22
  163. package/src/composer/ComposerPlusMenu.parity.spec.tsx +218 -233
  164. package/src/composer/MentionPopover.tsx +12 -3
  165. package/src/composer/PromptComposer.context.spec.tsx +527 -195
  166. package/src/composer/PromptComposer.tsx +1 -1
  167. package/src/composer/TiptapComposer.tsx +105 -37
  168. package/src/composer/use-mention-search.ts +8 -1
@@ -849,6 +849,63 @@ describe("AgentKitAssistantChat host behavior", () => {
849
849
  );
850
850
  });
851
851
 
852
+ it("uses a sanitized first-message fallback when the thread title is blank", async () => {
853
+ const savedSnapshots = vi.fn();
854
+ const message = {
855
+ id: "message-user-title-fallback",
856
+ role: "user",
857
+ status: "complete",
858
+ createdAt: "2026-10-07T12:00:00.000Z",
859
+ parts: [
860
+ {
861
+ type: "text",
862
+ text: 'Summarize @[the sprint|resource:123]\n<context data-agentkit-context-encoding="entities-v1">Private context</context>',
863
+ },
864
+ ],
865
+ };
866
+ chatMocks.thread = {
867
+ ...chatMocks.thread,
868
+ thread: {
869
+ id: chatMocks.threadId,
870
+ title: "",
871
+ createdAt: message.createdAt,
872
+ updatedAt: message.createdAt,
873
+ },
874
+ messages: [message],
875
+ events: [],
876
+ activeRunIds: [],
877
+ runs: {},
878
+ approvals: {},
879
+ approvalRunIds: {},
880
+ connectionRequests: {},
881
+ connectionRequestRunIds: {},
882
+ tools: {},
883
+ activities: {},
884
+ queuedMessages: [],
885
+ tasks: {},
886
+ taskGroups: {},
887
+ widgets: {},
888
+ widgetMessageIds: {},
889
+ annotations: {},
890
+ annotationMessageIds: {},
891
+ agents: {},
892
+ agentInteractions: [],
893
+ artifacts: [],
894
+ };
895
+
896
+ const props = baseProps({
897
+ onSaveThread: savedSnapshots,
898
+ runtime: {} as never,
899
+ });
900
+ await mount(props);
901
+ await act(async () => root.render(null));
902
+
903
+ expect(savedSnapshots).toHaveBeenCalledWith(
904
+ chatMocks.threadId,
905
+ expect.objectContaining({ title: "Summarize @the sprint" }),
906
+ );
907
+ });
908
+
852
909
  it("shows a retry when chat history fails to load", async () => {
853
910
  const retryHistory = vi.fn();
854
911
  chatMocks.history = {
@@ -897,6 +954,38 @@ describe("AgentKitAssistantChat host behavior", () => {
897
954
  expect(container.querySelector('[role="status"]')?.textContent).toBe(
898
955
  "agentChat.status.thinking",
899
956
  );
957
+ expect(
958
+ container.querySelector("[data-agentkit-active-run-id-copy]"),
959
+ ).toBeNull();
960
+ });
961
+
962
+ it("shows and copies the active run ID as soon as it becomes available", async () => {
963
+ const props = baseProps();
964
+ await mount(props);
965
+
966
+ expect(
967
+ container.querySelector("[data-agentkit-active-run-id-copy]"),
968
+ ).toBeNull();
969
+
970
+ chatMocks.thread = { ...chatMocks.thread, activeRunIds: ["run-active"] };
971
+ await act(async () => root.render(<AgentKitAssistantChat {...props} />));
972
+
973
+ const copyRequestIdButton = container.querySelector(
974
+ '[data-agentkit-active-run-id-copy] button[aria-label="agentChat.message.copyRequestId"]',
975
+ );
976
+ expect(copyRequestIdButton).toBeTruthy();
977
+
978
+ await act(async () => {
979
+ copyRequestIdButton?.click();
980
+ await Promise.resolve();
981
+ });
982
+
983
+ expect(chatMocks.writeClipboardText).toHaveBeenCalledWith("run-active");
984
+ expect(
985
+ container.querySelector(
986
+ '[data-agentkit-active-run-id-copy] button[aria-label="agentChat.common.copied"]',
987
+ ),
988
+ ).toBeTruthy();
900
989
  });
901
990
 
902
991
  it("does not duplicate Thinking after the run becomes active", async () => {
@@ -4015,11 +4104,18 @@ describe("AgentKitAssistantChat host behavior", () => {
4015
4104
  createdAt: "2026-09-26T12:00:01.000Z",
4016
4105
  parts: [{ type: "text", text: "Assistant answer survives handoff" }],
4017
4106
  };
4107
+ const followupUserMessage = {
4108
+ id: "handoff-followup-user-message",
4109
+ role: "user",
4110
+ status: "complete",
4111
+ createdAt: "2026-09-26T12:00:02.000Z",
4112
+ parts: [{ type: "text", text: "Save after handoff" }],
4113
+ };
4018
4114
  chatMocks.threadId = threadId;
4019
4115
  chatMocks.thread = {
4020
4116
  thread: {
4021
4117
  id: threadId,
4022
- title: "Handoff thread",
4118
+ title: "",
4023
4119
  createdAt: message.createdAt,
4024
4120
  updatedAt: message.createdAt,
4025
4121
  },
@@ -4056,7 +4152,13 @@ describe("AgentKitAssistantChat host behavior", () => {
4056
4152
 
4057
4153
  await act(async () => root.render(null));
4058
4154
  expect(savedSnapshots).toHaveBeenCalledOnce();
4059
-
4155
+ expect(savedSnapshots).toHaveBeenCalledWith(
4156
+ threadId,
4157
+ expect.objectContaining({
4158
+ title: "Keep this transcript visible",
4159
+ titleSource: "fallback",
4160
+ }),
4161
+ );
4060
4162
  const fetch = vi.fn(async () => new Response(null, { status: 404 }));
4061
4163
  const runtime = {
4062
4164
  id: "handoff-test",
@@ -4092,23 +4194,23 @@ describe("AgentKitAssistantChat host behavior", () => {
4092
4194
  chatMocks.createTransport.mockImplementation((options: any) =>
4093
4195
  createAgentNativeAgentKitTransport({ ...options, runtime, fetch }),
4094
4196
  );
4095
- await act(async () => {
4096
- root.render(
4097
- <AgentKitAssistantChat
4098
- {...baseProps({
4099
- threadId,
4100
- browserTabId,
4101
- isNewThread: false,
4102
- onSaveThread: savedSnapshots,
4103
- centerComposerWhenEmpty: true,
4104
- suggestionPlacement: "context-chips",
4105
- homeIntroSlot: <h1>What should we do?</h1>,
4106
- afterComposerSlot: <div data-testid="home-app-grid" />,
4107
- suggestions: ["Explore my apps"],
4108
- })}
4109
- />,
4110
- );
4197
+ const restoredProps = baseProps({
4198
+ threadId,
4199
+ browserTabId,
4200
+ isNewThread: false,
4201
+ onSaveThread: savedSnapshots,
4202
+ centerComposerWhenEmpty: true,
4203
+ suggestionPlacement: "context-chips",
4204
+ homeIntroSlot: <h1>What should we do?</h1>,
4205
+ afterComposerSlot: <div data-testid="home-app-grid" />,
4206
+ suggestions: ["Explore my apps"],
4111
4207
  });
4208
+ const restoredRef = createRef<any>();
4209
+ await act(async () =>
4210
+ root.render(
4211
+ <AgentKitAssistantChat {...restoredProps} ref={restoredRef} />,
4212
+ ),
4213
+ );
4112
4214
 
4113
4215
  expect(container.textContent).toContain("Keep this transcript visible");
4114
4216
  expect(container.textContent).toContain(
@@ -4122,6 +4224,10 @@ describe("AgentKitAssistantChat host behavior", () => {
4122
4224
  const handoff = await chatMocks.rootProps.transport.getThreadSnapshot({
4123
4225
  threadId,
4124
4226
  });
4227
+ expect(handoff).toMatchObject({
4228
+ title: "Keep this transcript visible",
4229
+ titleSource: "fallback",
4230
+ });
4125
4231
  expect(handoff.messages[0].parts).toContainEqual({
4126
4232
  type: "text",
4127
4233
  text: "Keep this transcript visible",
@@ -4131,6 +4237,22 @@ describe("AgentKitAssistantChat host behavior", () => {
4131
4237
  text: "Assistant answer survives handoff",
4132
4238
  });
4133
4239
  expect(fetch).not.toHaveBeenCalled();
4240
+
4241
+ chatMocks.thread = {
4242
+ ...chatMocks.thread,
4243
+ thread: handoff,
4244
+ messages: [...handoff.messages, followupUserMessage],
4245
+ };
4246
+ await act(async () =>
4247
+ root.render(
4248
+ <AgentKitAssistantChat {...restoredProps} ref={restoredRef} />,
4249
+ ),
4250
+ );
4251
+ const restoredSurfaceSnapshot = restoredRef.current?.exportThreadSnapshot();
4252
+ expect(restoredSurfaceSnapshot).toMatchObject({
4253
+ title: "Keep this transcript visible",
4254
+ titleSource: "fallback",
4255
+ });
4134
4256
  });
4135
4257
 
4136
4258
  it("keeps transient thread-restore errors retryable without clearing the tab", async () => {
@@ -83,9 +83,12 @@ import {
83
83
  realtimeVoiceTranscriptRegistry,
84
84
  type RealtimeVoiceTranscriptMessage,
85
85
  } from "@agent-native/toolkit/composer/realtime-voice-transcript";
86
+ import { IconButton } from "@agent-native/toolkit/design-system";
86
87
  import { cn } from "@agent-native/toolkit/utils";
87
88
  import {
88
89
  IconAlertTriangle,
90
+ IconCircleCheck,
91
+ IconCopy,
89
92
  IconLoader2,
90
93
  IconMessage,
91
94
  IconPlayerStopFilled,
@@ -169,7 +172,10 @@ import {
169
172
  } from "./chat/tool-call-display.js";
170
173
  import { resolveAgentKitToolSource } from "./chat/tool-integration.js";
171
174
  import { ExternalAgentNudge } from "./external-agent-host.js";
175
+ import { fallbackChatTitle } from "./fallback-chat-title.js";
176
+ import { formatFeedbackReport } from "./feedback-report.js";
172
177
  import { FileStorageSetupPopover } from "./FileStorageSetupPopover.js";
178
+ import { reconcileSettledRun } from "./reconcile-settled-run.js";
173
179
  import { RunStuckBanner } from "./RunStuckBanner.js";
174
180
  import { ThinkingDisplayProvider } from "./thinking-display.js";
175
181
 
@@ -200,9 +206,12 @@ const DEFERRED_PROVIDER_SUBMISSION_CLAIM_TTL_MS = 15 * 60 * 1000;
200
206
  const DEFERRED_PROVIDER_SUBMISSION_MAX_RETRIES = 3;
201
207
  const DEFERRED_PROVIDER_SUBMISSIONS_KEY_PREFIX =
202
208
  "agentkit-deferred-provider-submissions:";
209
+ type AgentKitHandoffThreadSnapshot = AgentThreadSnapshot & {
210
+ titleSource?: "fallback";
211
+ };
203
212
  const threadHandoffSnapshots = new Map<
204
213
  string,
205
- { snapshot: AgentThreadSnapshot; expiresAt: number }
214
+ { snapshot: AgentKitHandoffThreadSnapshot; expiresAt: number }
206
215
  >();
207
216
  const deferredProviderSubmissionOperations = new Map<
208
217
  string,
@@ -856,6 +865,11 @@ export const AgentKitAssistantChat = forwardRef<
856
865
  shortcut: "{{shortcut}}",
857
866
  }),
858
867
  feedbackSubmit: t("agentChat.feedback.submit"),
868
+ feedbackReasonMisread: t("agentChat.feedback.reasonMisread"),
869
+ feedbackReasonNotDone: t("agentChat.feedback.reasonNotDone"),
870
+ feedbackReasonWrongNumbers: t("agentChat.feedback.reasonWrongNumbers"),
871
+ feedbackReasonTooSlow: t("agentChat.feedback.tooSlow"),
872
+ feedbackCopyDetails: t("agentChat.feedback.copyDetails"),
859
873
  fork: t("agentChat.message.forkChat"),
860
874
  previousBranch: t("agentChat.message.previousBranch"),
861
875
  nextBranch: t("agentChat.message.nextBranch"),
@@ -863,6 +877,8 @@ export const AgentKitAssistantChat = forwardRef<
863
877
  error: t("agentChat.error.failed"),
864
878
  renderError: t("agentChat.error.render"),
865
879
  runFailed: t("agentChat.error.failed"),
880
+ continueRun: t("agentChat.common.continue"),
881
+ continueRunUnavailable: t("agentChat.recovery.continueUnavailable"),
866
882
  reconnect: t("agentChat.agentPanel.chatgptSubscriptionReconnect"),
867
883
  reasoning: t("agentChat.status.thinking"),
868
884
  expandActivity: t("agentChat.common.expand"),
@@ -1202,6 +1218,7 @@ export const AgentKitAssistantChat = forwardRef<
1202
1218
  labels={labels}
1203
1219
  branchNavigation={props.branchNavigation}
1204
1220
  loadRunUsage={loadRunUsage}
1221
+ buildFeedbackReport={formatFeedbackReport}
1205
1222
  onThreadForked={(thread) => props.onForkedThread?.(thread.id)}
1206
1223
  onCopyMessage={({ text }) => {
1207
1224
  const html = renderMarkdownToClipboardHtml(text);
@@ -1522,6 +1539,15 @@ const AgentKitAssistantChatBody = forwardRef<
1522
1539
  () => hasActiveAgentRuns(controller.getThread(threadId)),
1523
1540
  [controller, threadId],
1524
1541
  );
1542
+ const reconcileServerSettled = useCallback(
1543
+ () =>
1544
+ reconcileSettledRun({
1545
+ load: control.load,
1546
+ getThread: () => controller.getThread(threadId),
1547
+ tabId: props.tabId ?? threadId,
1548
+ }),
1549
+ [control.load, controller, props.tabId, threadId],
1550
+ );
1525
1551
 
1526
1552
  useEffect(() => {
1527
1553
  if (previousPrefillRevisionRef.current === prefillRevision) return;
@@ -3141,6 +3167,7 @@ const AgentKitAssistantChatBody = forwardRef<
3141
3167
  )
3142
3168
  }
3143
3169
  isAwaitingResponse={() => isRunning}
3170
+ onServerSettled={reconcileServerSettled}
3144
3171
  onRetry={() =>
3145
3172
  void sendRecoveryMessage(RECOVERY_CONTINUE_PROMPT, "continue")
3146
3173
  }
@@ -3369,6 +3396,32 @@ function AgentKitTranscript({ children, threadId }: AgentKitRegionRenderProps) {
3369
3396
  const threadMessageIds = new Set(
3370
3397
  thread.messages.map((message) => message.id),
3371
3398
  );
3399
+ const activeRunId = thread.activeRunIds.at(-1);
3400
+ const [requestIdCopyFeedback, setRequestIdCopyFeedback] = useState<{
3401
+ runId: string;
3402
+ status: "copied" | "failed";
3403
+ } | null>(null);
3404
+ const activeRunRequestIdCopyStatus =
3405
+ requestIdCopyFeedback && requestIdCopyFeedback.runId === activeRunId
3406
+ ? requestIdCopyFeedback.status
3407
+ : null;
3408
+ useEffect(() => {
3409
+ if (!requestIdCopyFeedback) return;
3410
+ const timeout = setTimeout(() => setRequestIdCopyFeedback(null), 1_400);
3411
+ return () => clearTimeout(timeout);
3412
+ }, [requestIdCopyFeedback]);
3413
+ const copyActiveRunRequestId = async () => {
3414
+ if (!activeRunId) return;
3415
+ try {
3416
+ const copied = await writeClipboardText(activeRunId);
3417
+ setRequestIdCopyFeedback({
3418
+ runId: activeRunId,
3419
+ status: copied ? "copied" : "failed",
3420
+ });
3421
+ } catch {
3422
+ setRequestIdCopyFeedback({ runId: activeRunId, status: "failed" });
3423
+ }
3424
+ };
3372
3425
  const lastMessage = thread.messages.at(-1);
3373
3426
  const showThinking =
3374
3427
  surface.isSubmissionInFlight &&
@@ -3538,6 +3591,46 @@ function AgentKitTranscript({ children, threadId }: AgentKitRegionRenderProps) {
3538
3591
  surface.props.tabId,
3539
3592
  )}
3540
3593
  {children}
3594
+ {activeRunId ? (
3595
+ <div
3596
+ className="agentkit-activities-static"
3597
+ data-agentkit-active-run-id-copy="true"
3598
+ >
3599
+ <IconButton
3600
+ label={
3601
+ activeRunRequestIdCopyStatus === "copied"
3602
+ ? t("agentChat.common.copied")
3603
+ : activeRunRequestIdCopyStatus === "failed"
3604
+ ? t("agentChat.recovery.copyFailed")
3605
+ : t("agentChat.message.copyRequestId")
3606
+ }
3607
+ title={t("agentChat.message.copyRequestId")}
3608
+ icon={
3609
+ activeRunRequestIdCopyStatus === "copied" ? (
3610
+ <IconCircleCheck aria-hidden="true" />
3611
+ ) : activeRunRequestIdCopyStatus === "failed" ? (
3612
+ <IconAlertTriangle aria-hidden="true" />
3613
+ ) : (
3614
+ <IconCopy aria-hidden="true" />
3615
+ )
3616
+ }
3617
+ size="compact"
3618
+ onPress={() => void copyActiveRunRequestId()}
3619
+ />
3620
+ {activeRunRequestIdCopyStatus ? (
3621
+ <span
3622
+ className="sr-only"
3623
+ role={
3624
+ activeRunRequestIdCopyStatus === "failed" ? "alert" : "status"
3625
+ }
3626
+ >
3627
+ {activeRunRequestIdCopyStatus === "copied"
3628
+ ? t("agentChat.common.copied")
3629
+ : t("agentChat.recovery.copyFailed")}
3630
+ </span>
3631
+ ) : null}
3632
+ </div>
3633
+ ) : null}
3541
3634
  {handoffMessages.map((message) => (
3542
3635
  <AgentMessageView
3543
3636
  key={message.id}
@@ -4607,6 +4700,7 @@ function AgentKitRunFailure({
4607
4700
  const surface = useAgentKitSurface();
4608
4701
  const t = useT();
4609
4702
  const [dismissed, setDismissed] = useState<string | null>(null);
4703
+ const [continueFailed, setContinueFailed] = useState(false);
4610
4704
  const authErrorReason =
4611
4705
  error.code === "unauthorized" || error.code === "http_401"
4612
4706
  ? "session-expired"
@@ -4690,6 +4784,17 @@ function AgentKitRunFailure({
4690
4784
  );
4691
4785
  }
4692
4786
  if (wasRetried(thread.messages, runId)) return null;
4787
+ // Continuing resumes the stopped run's own turn, so finished steps are not
4788
+ // run again; that needs it to still be the turn's newest run.
4789
+ const continueStoppedRun = control.canContinueRun
4790
+ ? superseded
4791
+ ? undefined
4792
+ : () => {
4793
+ setContinueFailed(false);
4794
+ control.continueRun(runId).catch(() => setContinueFailed(true));
4795
+ }
4796
+ : () =>
4797
+ void surface.sendRecoveryMessage(RECOVERY_CONTINUE_PROMPT, "continue");
4693
4798
  const info: RunErrorInfo = {
4694
4799
  message: formatAgentKitErrorText(error, t),
4695
4800
  errorCode: error.code,
@@ -4700,8 +4805,9 @@ function AgentKitRunFailure({
4700
4805
  return (
4701
4806
  <RunErrorRecoveryCard
4702
4807
  info={info}
4703
- onContinue={() =>
4704
- void surface.sendRecoveryMessage(RECOVERY_CONTINUE_PROMPT, "continue")
4808
+ onContinue={continueStoppedRun}
4809
+ continueError={
4810
+ continueFailed ? t("agentChat.recovery.continueUnavailable") : null
4705
4811
  }
4706
4812
  onRetry={() => void retryFailedTurn()}
4707
4813
  retryHasUnavailableAttachment={retryRequest.hasUnavailableAttachment}
@@ -5334,7 +5440,13 @@ function createAgentKitThreadSnapshot(thread: AgentThreadState) {
5334
5440
  agentMessageText(
5335
5441
  [...messages].reverse().find((message) => message.role === "user")!,
5336
5442
  );
5337
- const title = thread.thread?.title ?? firstUserText?.slice(0, 80) ?? "";
5443
+ const handoffThread = thread.thread as AgentKitHandoffThreadSnapshot | null;
5444
+ const savedTitle = handoffThread?.title?.trim();
5445
+ const titleSource: "fallback" | undefined =
5446
+ !savedTitle || handoffThread?.titleSource === "fallback"
5447
+ ? "fallback"
5448
+ : undefined;
5449
+ const title = savedTitle || fallbackChatTitle(firstUserText ?? "");
5338
5450
  const runs = Object.entries(thread.runs).map(([id, run]) => ({
5339
5451
  ...run,
5340
5452
  id,
@@ -5356,6 +5468,7 @@ function createAgentKitThreadSnapshot(thread: AgentThreadState) {
5356
5468
  agentKit,
5357
5469
  }),
5358
5470
  title,
5471
+ ...(titleSource ? { titleSource } : {}),
5359
5472
  preview: (latestUserText ?? "").slice(0, 280),
5360
5473
  messageCount: messages.length,
5361
5474
  };
@@ -5389,7 +5502,7 @@ function createAgentKitThreadHandoffKey(
5389
5502
  function readAgentKitThreadHandoffSnapshot(
5390
5503
  key: string,
5391
5504
  consume = false,
5392
- ): AgentThreadSnapshot | null {
5505
+ ): AgentKitHandoffThreadSnapshot | null {
5393
5506
  const entry = threadHandoffSnapshots.get(key);
5394
5507
  if (!entry) return null;
5395
5508
  if (entry.expiresAt <= Date.now()) {
@@ -5413,10 +5526,13 @@ function storeAgentKitThreadHandoffSnapshot(
5413
5526
  if (!messages.length) return;
5414
5527
 
5415
5528
  const now = new Date().toISOString();
5416
- const handoff: AgentThreadSnapshot = {
5529
+ const handoff: AgentKitHandoffThreadSnapshot = {
5417
5530
  ...(thread.thread ?? {}),
5418
5531
  id: thread.id,
5419
5532
  title: snapshot.title || thread.thread?.title,
5533
+ ...(snapshot.titleSource === "fallback"
5534
+ ? { titleSource: "fallback" as const }
5535
+ : {}),
5420
5536
  createdAt: thread.thread?.createdAt ?? messages[0]?.createdAt ?? now,
5421
5537
  updatedAt: thread.thread?.updatedAt ?? messages.at(-1)?.createdAt ?? now,
5422
5538
  messages,
@@ -5617,7 +5733,7 @@ function appendVoiceTranscriptsToThreadSnapshot(
5617
5733
  }),
5618
5734
  title:
5619
5735
  snapshot.title ||
5620
- (firstUser ? agentMessageText(firstUser).slice(0, 80) : ""),
5736
+ (firstUser ? fallbackChatTitle(agentMessageText(firstUser)) : ""),
5621
5737
  preview: latestUser ? agentMessageText(latestUser).slice(0, 280) : "",
5622
5738
  messageCount: messages.length,
5623
5739
  };
@@ -9,7 +9,7 @@ import {
9
9
  } from "@agent-native/toolkit/ui/dropdown-menu";
10
10
  import React, { act } from "react";
11
11
  import { createRoot, type Root } from "react-dom/client";
12
- import { MemoryRouter, useLocation } from "react-router";
12
+ import { MemoryRouter, useHref, useLocation } from "react-router";
13
13
  import { describe, expect, it, vi } from "vitest";
14
14
 
15
15
  import {
@@ -445,6 +445,45 @@ describe("AgentPanel settings navigation", () => {
445
445
  }
446
446
  });
447
447
 
448
+ it("keeps a settings request inside the mounted app path", () => {
449
+ const container = document.createElement("div");
450
+ document.body.appendChild(container);
451
+ const root: Root = createRoot(container);
452
+ let href = "";
453
+
454
+ function LocationProbe() {
455
+ const location = useLocation();
456
+ href = useHref(`${location.pathname}${location.hash}`);
457
+ return null;
458
+ }
459
+
460
+ try {
461
+ vi.stubEnv("VITE_APP_BASE_PATH", "/dispatch");
462
+ act(() => {
463
+ window.history.replaceState(null, "", "/dispatch/chat#llm");
464
+ root.render(
465
+ React.createElement(
466
+ MemoryRouter,
467
+ { basename: "/dispatch", initialEntries: ["/dispatch/chat#llm"] },
468
+ React.createElement(AgentPanelSettingsNavigation),
469
+ React.createElement(LocationProbe),
470
+ ),
471
+ );
472
+ });
473
+
474
+ act(() => {
475
+ window.dispatchEvent(new CustomEvent("agent-panel:open-settings"));
476
+ });
477
+
478
+ expect(href).toBe("/dispatch/settings#llm");
479
+ } finally {
480
+ act(() => root.unmount());
481
+ container.remove();
482
+ window.history.replaceState(null, "", "/");
483
+ vi.unstubAllEnvs();
484
+ }
485
+ });
486
+
448
487
  it("routes a mounted settings request to an existing secret-specific hash", () => {
449
488
  const container = document.createElement("div");
450
489
  document.body.appendChild(container);
@@ -532,7 +571,7 @@ describe("AgentPanel settings navigation", () => {
532
571
  }
533
572
  });
534
573
 
535
- it("preserves the app base path when opening settings", () => {
574
+ it("navigates with a router-local settings path", () => {
536
575
  const container = document.createElement("div");
537
576
  document.body.appendChild(container);
538
577
  const root: Root = createRoot(container);
@@ -545,11 +584,10 @@ describe("AgentPanel settings navigation", () => {
545
584
 
546
585
  try {
547
586
  act(() => {
548
- window.history.replaceState(null, "", "/dispatch/_agent-native/poll");
549
587
  root.render(
550
588
  React.createElement(
551
589
  MemoryRouter,
552
- { initialEntries: ["/"] },
590
+ { initialEntries: ["/chat"] },
553
591
  React.createElement(AgentPanelSettingsNavigation),
554
592
  React.createElement(LocationProbe),
555
593
  ),
@@ -564,11 +602,10 @@ describe("AgentPanel settings navigation", () => {
564
602
  );
565
603
  });
566
604
 
567
- expect(pathname).toBe("/dispatch/settings");
605
+ expect(pathname).toBe("/settings");
568
606
  } finally {
569
607
  act(() => root.unmount());
570
608
  container.remove();
571
- window.history.replaceState(null, "", "/");
572
609
  }
573
610
  });
574
611
 
@@ -1134,7 +1171,6 @@ describe("AgentChatSurface chrome defaults", () => {
1134
1171
  expect(source).not.toContain("SettingsPanel");
1135
1172
  expect(source).not.toContain("allowSettingsMode");
1136
1173
  expect(source).not.toContain('mode === "settings"');
1137
- expect(source).toContain('pathname: appPath("/settings")');
1138
1174
  });
1139
1175
 
1140
1176
  it("mounts URL command sync for a full-page chat surface", () => {
@@ -199,7 +199,7 @@ export function AgentPanelSettingsNavigation({
199
199
  const requested = requestedSettingsSection(section, window.location.hash);
200
200
  const navigation = navigate(
201
201
  {
202
- pathname: appPath("/settings"),
202
+ pathname: "/settings",
203
203
  hash: settingsRouteHashForSection(section, window.location.hash),
204
204
  },
205
205
  // The hash can't tell API keys from Integrations; the redesigned
@@ -85,6 +85,13 @@ const assistantChatMockState = vi.hoisted(() => ({
85
85
  onRetryModelList: undefined as (() => void) | undefined,
86
86
  onSlashCommand: undefined as ((command: string) => void) | undefined,
87
87
  onForkedThread: undefined as ((threadId: string) => void) | undefined,
88
+ onGenerateTitle: undefined as
89
+ | ((
90
+ threadId: string,
91
+ message: string,
92
+ selection: { engine?: string; model?: string },
93
+ ) => void)
94
+ | undefined,
88
95
  onSaveThread: undefined as
89
96
  | ((
90
97
  threadId: string,
@@ -93,6 +100,7 @@ const assistantChatMockState = vi.hoisted(() => ({
93
100
  title: string;
94
101
  preview: string;
95
102
  messageCount: number;
103
+ titleSource?: "fallback";
96
104
  },
97
105
  ) => void)
98
106
  | undefined,
@@ -351,6 +359,7 @@ vi.mock("./AgentKitAssistantChat.js", async () => {
351
359
  onRetryModelList?: () => void;
352
360
  onSlashCommand?: (command: string) => void;
353
361
  onForkedThread?: (threadId: string) => void;
362
+ onGenerateTitle?: typeof assistantChatMockState.onGenerateTitle;
354
363
  onSaveThread?: typeof assistantChatMockState.onSaveThread;
355
364
  branchNavigation?: typeof assistantChatMockState.branchNavigation;
356
365
  };
@@ -359,6 +368,7 @@ vi.mock("./AgentKitAssistantChat.js", async () => {
359
368
  assistantChatMockState.onRetryModelList = props.onRetryModelList;
360
369
  assistantChatMockState.onSlashCommand = props.onSlashCommand;
361
370
  assistantChatMockState.onForkedThread = props.onForkedThread;
371
+ assistantChatMockState.onGenerateTitle = props.onGenerateTitle;
362
372
  assistantChatMockState.onSaveThread = props.onSaveThread;
363
373
  assistantChatMockState.branchNavigation = props.branchNavigation;
364
374
  React.useImperativeHandle(ref, () => ({
@@ -413,6 +423,7 @@ function resetThreadMocks() {
413
423
  assistantChatMockState.onRetryModelList = undefined;
414
424
  assistantChatMockState.onSlashCommand = undefined;
415
425
  assistantChatMockState.onForkedThread = undefined;
426
+ assistantChatMockState.onGenerateTitle = undefined;
416
427
  assistantChatMockState.onSaveThread = undefined;
417
428
  assistantChatMockState.branchNavigation = undefined;
418
429
  threadMocks.activeThreadId = "thread-1";
@@ -522,6 +533,7 @@ describe("MultiTabAssistantChat postMessage bridge", () => {
522
533
  title: "Saved chat",
523
534
  preview: "Latest request",
524
535
  messageCount: 1,
536
+ titleSource: "fallback" as const,
525
537
  };
526
538
  window.history.replaceState(null, "", "/");
527
539
 
@@ -583,6 +595,28 @@ describe("MultiTabAssistantChat postMessage bridge", () => {
583
595
  expect(window.location.search).toBe("?thread=thread-1");
584
596
  });
585
597
 
598
+ it("persists a sanitized prompt title when title generation is unavailable", async () => {
599
+ threadMocks.generateTitle.mockResolvedValueOnce(null);
600
+ await act(async () => {
601
+ root.render(<MultiTabAssistantChat storageKey="title-fallback" />);
602
+ await Promise.resolve();
603
+ });
604
+
605
+ const message =
606
+ 'Summarize @[the sprint|resource:123]\n<context data-agentkit-context-encoding="entities-v1">Private context</context>';
607
+ await act(async () => {
608
+ assistantChatMockState.onGenerateTitle?.("thread-1", message, {});
609
+ await Promise.resolve();
610
+ });
611
+
612
+ expect(threadMocks.saveThreadData).toHaveBeenCalledWith("thread-1", {
613
+ threadData: "",
614
+ title: "Summarize @the sprint",
615
+ preview: message.slice(0, 120),
616
+ titleSource: "fallback",
617
+ });
618
+ });
619
+
586
620
  it("prefills the active composer without submitting when submit is false", () => {
587
621
  act(() => {
588
622
  dispatchSubmitChat({