@agent-native/toolkit 0.205.0 → 0.206.0-nightly-20261009011246

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 (143) hide show
  1. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  2. package/dist/app/agentkit/react/components.js +17 -9
  3. package/dist/app/agentkit/react/components.js.map +1 -1
  4. package/dist/app/auth/AuthPage.d.ts.map +1 -1
  5. package/dist/app/auth/AuthPage.js +11 -0
  6. package/dist/app/auth/AuthPage.js.map +1 -1
  7. package/dist/app/chat/AgentKitAssistantChat.d.ts +2 -0
  8. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  9. package/dist/app/chat/AgentKitAssistantChat.js +24 -14
  10. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  11. package/dist/app/chat/AgentPanel.d.ts.map +1 -1
  12. package/dist/app/chat/AgentPanel.js +3 -2
  13. package/dist/app/chat/AgentPanel.js.map +1 -1
  14. package/dist/app/chat/MultiTabAssistantChat.d.ts +4 -1
  15. package/dist/app/chat/MultiTabAssistantChat.d.ts.map +1 -1
  16. package/dist/app/chat/MultiTabAssistantChat.js +61 -3
  17. package/dist/app/chat/MultiTabAssistantChat.js.map +1 -1
  18. package/dist/app/chat/agentkit-chat/approval-card.d.ts.map +1 -1
  19. package/dist/app/chat/agentkit-chat/approval-card.js +2 -1
  20. package/dist/app/chat/agentkit-chat/approval-card.js.map +1 -1
  21. package/dist/app/chat/chat/run-recovery.d.ts.map +1 -1
  22. package/dist/app/chat/chat/run-recovery.js +74 -8
  23. package/dist/app/chat/chat/run-recovery.js.map +1 -1
  24. package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
  25. package/dist/app/chat/chat/tool-call-display.js +6 -4
  26. package/dist/app/chat/chat/tool-call-display.js.map +1 -1
  27. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.d.ts.map +1 -1
  28. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.js +2 -1
  29. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.js.map +1 -1
  30. package/dist/app/chat/conversation/AgentConversation.d.ts.map +1 -1
  31. package/dist/app/chat/conversation/AgentConversation.js +10 -7
  32. package/dist/app/chat/conversation/AgentConversation.js.map +1 -1
  33. package/dist/app/chat/fallback-chat-title.d.ts.map +1 -1
  34. package/dist/app/chat/fallback-chat-title.js +2 -4
  35. package/dist/app/chat/fallback-chat-title.js.map +1 -1
  36. package/dist/app/chat/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  37. package/dist/app/chat/mcp-apps/McpAppRenderer.js +3 -2
  38. package/dist/app/chat/mcp-apps/McpAppRenderer.js.map +1 -1
  39. package/dist/app/chat/session-replay-privacy.d.ts +7 -0
  40. package/dist/app/chat/session-replay-privacy.d.ts.map +1 -0
  41. package/dist/app/chat/session-replay-privacy.js +6 -0
  42. package/dist/app/chat/session-replay-privacy.js.map +1 -0
  43. package/dist/app/extensions/ExtensionViewer.d.ts +4 -1
  44. package/dist/app/extensions/ExtensionViewer.d.ts.map +1 -1
  45. package/dist/app/extensions/ExtensionViewer.js +3 -3
  46. package/dist/app/extensions/ExtensionViewer.js.map +1 -1
  47. package/dist/app/extensions/ExtensionViewerPage.d.ts +4 -1
  48. package/dist/app/extensions/ExtensionViewerPage.d.ts.map +1 -1
  49. package/dist/app/extensions/ExtensionViewerPage.js +2 -2
  50. package/dist/app/extensions/ExtensionViewerPage.js.map +1 -1
  51. package/dist/app/onboarding/FirstRunOnboarding.d.ts.map +1 -1
  52. package/dist/app/onboarding/FirstRunOnboarding.js +53 -12
  53. package/dist/app/onboarding/FirstRunOnboarding.js.map +1 -1
  54. package/dist/app/onboarding/OnboardingPanel.d.ts.map +1 -1
  55. package/dist/app/onboarding/OnboardingPanel.js +14 -4
  56. package/dist/app/onboarding/OnboardingPanel.js.map +1 -1
  57. package/dist/app/resources/ResourceEditor.d.ts +4 -1
  58. package/dist/app/resources/ResourceEditor.d.ts.map +1 -1
  59. package/dist/app/resources/ResourceEditor.js +18 -7
  60. package/dist/app/resources/ResourceEditor.js.map +1 -1
  61. package/dist/app/resources/ResourcesPanel.d.ts +4 -1
  62. package/dist/app/resources/ResourcesPanel.d.ts.map +1 -1
  63. package/dist/app/resources/ResourcesPanel.js +18 -18
  64. package/dist/app/resources/ResourcesPanel.js.map +1 -1
  65. package/dist/app/resources/custom-agent-model-options.d.ts +16 -0
  66. package/dist/app/resources/custom-agent-model-options.d.ts.map +1 -0
  67. package/dist/app/resources/custom-agent-model-options.js +53 -0
  68. package/dist/app/resources/custom-agent-model-options.js.map +1 -0
  69. package/dist/app/settings/SettingsPanel.d.ts.map +1 -1
  70. package/dist/app/settings/SettingsPanel.js +34 -3
  71. package/dist/app/settings/SettingsPanel.js.map +1 -1
  72. package/dist/app/settings/SettingsTabsPage.d.ts.map +1 -1
  73. package/dist/app/settings/SettingsTabsPage.js +2 -0
  74. package/dist/app/settings/SettingsTabsPage.js.map +1 -1
  75. package/dist/app/settings/api-keys/ApiKeyDialogs.d.ts.map +1 -1
  76. package/dist/app/settings/api-keys/ApiKeyDialogs.js +49 -9
  77. package/dist/app/settings/api-keys/ApiKeyDialogs.js.map +1 -1
  78. package/dist/app/settings/api-keys/ApiKeysSettingsPage.js +2 -1
  79. package/dist/app/settings/api-keys/ApiKeysSettingsPage.js.map +1 -1
  80. package/dist/app/settings/model/ModelSettingsPage.d.ts.map +1 -1
  81. package/dist/app/settings/model/ModelSettingsPage.js +43 -9
  82. package/dist/app/settings/model/ModelSettingsPage.js.map +1 -1
  83. package/dist/app/settings/model/ProviderDialog.d.ts +4 -0
  84. package/dist/app/settings/model/ProviderDialog.d.ts.map +1 -1
  85. package/dist/app/settings/model/ProviderDialog.js +107 -11
  86. package/dist/app/settings/model/ProviderDialog.js.map +1 -1
  87. package/dist/app/settings/useBuilderStatus.d.ts.map +1 -1
  88. package/dist/app/settings/useBuilderStatus.js +13 -10
  89. package/dist/app/settings/useBuilderStatus.js.map +1 -1
  90. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  91. package/dist/composer/TiptapComposer.js +51 -13
  92. package/dist/composer/TiptapComposer.js.map +1 -1
  93. package/package.json +4 -4
  94. package/src/app/agentkit/react/components.interaction.spec.tsx +229 -1
  95. package/src/app/agentkit/react/components.spec.tsx +42 -1
  96. package/src/app/agentkit/react/components.tsx +46 -12
  97. package/src/app/auth/AuthPage.tsx +13 -0
  98. package/src/app/chat/AgentKitAssistantChat.spec.tsx +116 -5
  99. package/src/app/chat/AgentKitAssistantChat.tsx +41 -20
  100. package/src/app/chat/AgentPanel.tsx +5 -1
  101. package/src/app/chat/MultiTabAssistantChat.spec.tsx +152 -7
  102. package/src/app/chat/MultiTabAssistantChat.tsx +71 -4
  103. package/src/app/chat/agentkit-chat/approval-card.spec.tsx +17 -0
  104. package/src/app/chat/agentkit-chat/approval-card.tsx +2 -0
  105. package/src/app/chat/chat/run-recovery.spec.tsx +354 -9
  106. package/src/app/chat/chat/run-recovery.tsx +178 -10
  107. package/src/app/chat/chat/tool-call-display.spec.tsx +77 -0
  108. package/src/app/chat/chat/tool-call-display.tsx +23 -6
  109. package/src/app/chat/chat-first/ChatFirstAgentActivityPanel.tsx +2 -1
  110. package/src/app/chat/conversation/AgentConversation.spec.tsx +190 -1
  111. package/src/app/chat/conversation/AgentConversation.tsx +32 -8
  112. package/src/app/chat/fallback-chat-title.spec.ts +44 -0
  113. package/src/app/chat/fallback-chat-title.ts +3 -4
  114. package/src/app/chat/mcp-apps/McpAppRenderer.spec.ts +24 -0
  115. package/src/app/chat/mcp-apps/McpAppRenderer.tsx +10 -2
  116. package/src/app/chat/session-replay-privacy.spec.ts +14 -0
  117. package/src/app/chat/session-replay-privacy.ts +5 -0
  118. package/src/app/extensions/ExtensionViewer.tsx +15 -3
  119. package/src/app/extensions/ExtensionViewerPage.tsx +7 -3
  120. package/src/app/onboarding/FirstRunOnboarding.spec.tsx +252 -2
  121. package/src/app/onboarding/FirstRunOnboarding.tsx +56 -9
  122. package/src/app/onboarding/OnboardingPanel.save-scope.spec.tsx +1 -0
  123. package/src/app/onboarding/OnboardingPanel.tsx +15 -3
  124. package/src/app/resources/ResourceEditor.spec.tsx +240 -5
  125. package/src/app/resources/ResourceEditor.tsx +57 -10
  126. package/src/app/resources/ResourcesPanel.spec.ts +86 -4
  127. package/src/app/resources/ResourcesPanel.tsx +49 -16
  128. package/src/app/resources/custom-agent-model-options.ts +92 -0
  129. package/src/app/settings/SettingsPanel.model-defaults.spec.tsx +26 -13
  130. package/src/app/settings/SettingsPanel.tsx +35 -3
  131. package/src/app/settings/SettingsTabsPage.spec.tsx +97 -0
  132. package/src/app/settings/SettingsTabsPage.tsx +2 -0
  133. package/src/app/settings/api-keys/ApiKeyDialogs.tsx +84 -22
  134. package/src/app/settings/api-keys/ApiKeysSettingsPage.spec.tsx +229 -1
  135. package/src/app/settings/api-keys/ApiKeysSettingsPage.tsx +3 -2
  136. package/src/app/settings/model/ModelSettingsPage.spec.tsx +99 -9
  137. package/src/app/settings/model/ModelSettingsPage.tsx +79 -11
  138. package/src/app/settings/model/ProviderDialog.spec.tsx +192 -1
  139. package/src/app/settings/model/ProviderDialog.tsx +225 -29
  140. package/src/app/settings/useBuilderStatus.spec.tsx +49 -0
  141. package/src/app/settings/useBuilderStatus.ts +16 -10
  142. package/src/composer/TiptapComposer.spec.ts +97 -2
  143. package/src/composer/TiptapComposer.tsx +67 -13
@@ -66,7 +66,10 @@ import { buildSignInReturnHref } from "@agent-native/core/client/sign-in-return"
66
66
  import { useFileUploadStatus } from "@agent-native/core/client/uploads";
67
67
  import { useSession } from "@agent-native/core/client/use-session";
68
68
  import { AGENTKIT_CHAT_MIGRATION_GUIDE_URL } from "@agent-native/core/package-lifecycle/migration-message";
69
- import { splitAgentChatContextFromMessage } from "@agent-native/core/shared";
69
+ import {
70
+ splitAgentChatContextFromMessage,
71
+ stripAgentChatContextFromMessage,
72
+ } from "@agent-native/core/shared";
70
73
  import { writeClipboardText } from "@agent-native/toolkit/clipboard";
71
74
  import {
72
75
  AgentSuggestionBar,
@@ -177,6 +180,7 @@ import { formatFeedbackReport } from "./feedback-report.js";
177
180
  import { FileStorageSetupPopover } from "./FileStorageSetupPopover.js";
178
181
  import { reconcileSettledRun } from "./reconcile-settled-run.js";
179
182
  import { RunStuckBanner } from "./RunStuckBanner.js";
183
+ import { SESSION_REPLAY_MASK_PROPS } from "./session-replay-privacy.js";
180
184
  import { ThinkingDisplayProvider } from "./thinking-display.js";
181
185
 
182
186
  export interface AgentKitAssistantChatProps extends AssistantChatProps {
@@ -4333,7 +4337,10 @@ function AgentKitComposerSurface({
4333
4337
  className="mx-3 mb-1.5 flex shrink-0 items-start gap-2 rounded-md border border-border bg-muted/70 px-3 py-2 text-xs text-foreground shadow-sm"
4334
4338
  >
4335
4339
  <IconAlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" />
4336
- <span className="min-w-0 flex-1 break-words leading-snug">
4340
+ <span
4341
+ {...SESSION_REPLAY_MASK_PROPS}
4342
+ className="min-w-0 flex-1 break-words leading-snug"
4343
+ >
4337
4344
  {composerError}
4338
4345
  </span>
4339
4346
  <button
@@ -5044,7 +5051,9 @@ function AgentKitConnectionError({
5044
5051
  </a>
5045
5052
  </>
5046
5053
  ) : (
5047
- <span>{formatAgentKitErrorText(error, t)}</span>
5054
+ <span {...SESSION_REPLAY_MASK_PROPS}>
5055
+ {formatAgentKitErrorText(error, t)}
5056
+ </span>
5048
5057
  )}
5049
5058
  {error.retryable ? (
5050
5059
  <button
@@ -5059,7 +5068,11 @@ function AgentKitConnectionError({
5059
5068
  </button>
5060
5069
  ) : null}
5061
5070
  {recoveryError ? (
5062
- <span className="mt-2 block" role="alert">
5071
+ <span
5072
+ {...SESSION_REPLAY_MASK_PROPS}
5073
+ className="mt-2 block"
5074
+ role="alert"
5075
+ >
5063
5076
  {formatAgentKitErrorText(
5064
5077
  { code: "runtime_error", message: recoveryError.message },
5065
5078
  t,
@@ -5390,17 +5403,27 @@ async function uploadAgentChatAttachments(
5390
5403
  );
5391
5404
  }
5392
5405
 
5393
- function agentMessageText(message: AgentMessage): string {
5394
- return message.parts
5406
+ function rawAgentMessageTextFromParts(parts: AgentMessage["parts"]): string {
5407
+ return parts
5395
5408
  .filter((part) => part.type === "text")
5396
- .map((part) =>
5397
- part.type === "text"
5398
- ? splitAgentChatContextFromMessage(part.text).message
5399
- : "",
5400
- )
5409
+ .map((part) => (part.type === "text" ? part.text : ""))
5401
5410
  .join("\n");
5402
5411
  }
5403
5412
 
5413
+ export function agentMessageTextFromParts(
5414
+ parts: AgentMessage["parts"],
5415
+ ): string {
5416
+ return stripAgentChatContextFromMessage(rawAgentMessageTextFromParts(parts));
5417
+ }
5418
+
5419
+ function rawAgentMessageText(message: AgentMessage): string {
5420
+ return rawAgentMessageTextFromParts(message.parts);
5421
+ }
5422
+
5423
+ function agentMessageText(message: AgentMessage): string {
5424
+ return agentMessageTextFromParts(message.parts);
5425
+ }
5426
+
5404
5427
  function persistedAgentMessage(message: AgentMessage): AgentMessage {
5405
5428
  return {
5406
5429
  ...message,
@@ -5432,14 +5455,12 @@ function createAgentKitThreadSnapshot(thread: AgentThreadState) {
5432
5455
  createdAt: message.createdAt,
5433
5456
  },
5434
5457
  }));
5435
- const firstUserText =
5436
- messages.find((message) => message.role === "user") &&
5437
- agentMessageText(messages.find((message) => message.role === "user")!);
5438
- const latestUserText =
5439
- [...messages].reverse().find((message) => message.role === "user") &&
5440
- agentMessageText(
5441
- [...messages].reverse().find((message) => message.role === "user")!,
5442
- );
5458
+ const firstUser = messages.find((message) => message.role === "user");
5459
+ const latestUser = [...messages]
5460
+ .reverse()
5461
+ .find((message) => message.role === "user");
5462
+ const firstUserText = firstUser && rawAgentMessageText(firstUser);
5463
+ const latestUserText = latestUser && agentMessageText(latestUser);
5443
5464
  const handoffThread = thread.thread as AgentKitHandoffThreadSnapshot | null;
5444
5465
  const savedTitle = handoffThread?.title?.trim();
5445
5466
  const titleSource: "fallback" | undefined =
@@ -5733,7 +5754,7 @@ function appendVoiceTranscriptsToThreadSnapshot(
5733
5754
  }),
5734
5755
  title:
5735
5756
  snapshot.title ||
5736
- (firstUser ? fallbackChatTitle(agentMessageText(firstUser)) : ""),
5757
+ (firstUser ? fallbackChatTitle(rawAgentMessageText(firstUser)) : ""),
5737
5758
  preview: latestUser ? agentMessageText(latestUser).slice(0, 280) : "",
5738
5759
  messageCount: messages.length,
5739
5760
  };
@@ -1,3 +1,4 @@
1
+ import type { ModelEngineConfig } from "@agent-native/core/agent/model-version";
1
2
  import { getBrowserTabId } from "@agent-native/core/client/hooks";
2
3
  import { buildSettingsRoute } from "@agent-native/core/navigation";
3
4
  import {
@@ -767,6 +768,8 @@ function AgentPanelInner({
767
768
  const t = useT();
768
769
  const location = useLocation();
769
770
  const mounted = useClientOnly();
771
+ const [activeChatModelEngine, setActiveChatModelEngine] =
772
+ useState<ModelEngineConfig | null>(null);
770
773
  const onboardingPreviewMode = useOnboardingPreviewMode();
771
774
  const firstRunOnboardingGateOwnsSurface =
772
775
  useFirstRunOnboardingGateOwnsSurface();
@@ -2402,6 +2405,7 @@ function AgentPanelInner({
2402
2405
  emptyStateAddon={emptyStateAddon}
2403
2406
  emptyStateFooter={emptyStateFooter}
2404
2407
  onMessageCountChange={onMessageCountChange}
2408
+ onActiveModelEngineChange={setActiveChatModelEngine}
2405
2409
  suggestions={suggestions}
2406
2410
  dynamicSuggestions={dynamicSuggestions}
2407
2411
  suggestionPlacement={
@@ -2493,7 +2497,7 @@ function AgentPanelInner({
2493
2497
  </div>
2494
2498
  }
2495
2499
  >
2496
- <ResourcesPanel />
2500
+ <ResourcesPanel modelEngine={activeChatModelEngine} />
2497
2501
  </Suspense>
2498
2502
  </div>
2499
2503
  )}
@@ -18,6 +18,7 @@ import {
18
18
  sendToAgentChat,
19
19
  } from "@agent-native/core/client/agent-chat";
20
20
  import { CHAT_MODEL_SELECTION_CHANGED_EVENT } from "@agent-native/core/client/agent-chat";
21
+ import { chatModelSelectionStorageKey } from "@agent-native/core/client/agent-chat";
21
22
  import type {
22
23
  ChatThreadScope,
23
24
  ChatThreadSummary,
@@ -116,6 +117,7 @@ const assistantChatMockState = vi.hoisted(() => ({
116
117
 
117
118
  const threadMocks = vi.hoisted(() => ({
118
119
  activeThreadId: "thread-1" as string | null,
120
+ isLoading: false,
119
121
  evictedThreadIds: [] as string[],
120
122
  threads: [
121
123
  {
@@ -216,12 +218,14 @@ const ANTHROPIC_ENGINES = [
216
218
  {
217
219
  name: "anthropic",
218
220
  label: "Claude",
221
+ defaultModel: "claude-sonnet-5-5",
219
222
  supportedModels: ["claude-sonnet-5"],
220
223
  requiredEnvVars: ["ANTHROPIC_API_KEY"],
221
224
  },
222
225
  {
223
226
  name: "ai-sdk:openai",
224
227
  label: "OpenAI",
228
+ defaultModel: "gpt-5.6-luna",
225
229
  supportedModels: ["gpt-5.6-luna"],
226
230
  requiredEnvVars: ["OPENAI_API_KEY"],
227
231
  },
@@ -246,6 +250,28 @@ function stubCatalog(
246
250
  builderConfigured = false,
247
251
  ) {
248
252
  invalidateClientStatusRequests();
253
+ const modelEngines = Object.fromEntries(
254
+ (
255
+ engines as Array<{
256
+ name: string;
257
+ label: string;
258
+ defaultModel?: string;
259
+ supportedModels?: string[];
260
+ acceptsCustomModels?: boolean;
261
+ preserveCustomModels?: boolean;
262
+ }>
263
+ ).map((engine) => [
264
+ engine.name,
265
+ {
266
+ name: engine.name,
267
+ label: engine.label,
268
+ defaultModel: engine.defaultModel ?? engine.supportedModels?.[0] ?? "",
269
+ supportedModels: engine.supportedModels ?? [],
270
+ ...(engine.acceptsCustomModels ? { acceptsCustomModels: true } : {}),
271
+ ...(engine.preserveCustomModels ? { preserveCustomModels: true } : {}),
272
+ },
273
+ ]),
274
+ );
249
275
  modelCatalogMocks.load = async () => ({
250
276
  state: "available",
251
277
  groups: buildChatModelGroups({
@@ -253,6 +279,8 @@ function stubCatalog(
253
279
  configuredKeys,
254
280
  builderConnected: builderConfigured,
255
281
  }),
282
+ modelEngines,
283
+ currentModelEngine: null,
256
284
  defaultModel: "gpt-5-6-luna",
257
285
  loadLiveGroups: async () => null,
258
286
  });
@@ -353,6 +381,8 @@ vi.mock("./AgentKitAssistantChat.js", async () => {
353
381
  composerDisabled?: boolean;
354
382
  composerDisabledPlaceholder?: string;
355
383
  isActiveComposer?: boolean;
384
+ isNewThread?: boolean;
385
+ isThreadStateLoading?: boolean;
356
386
  contextScope?: ChatThreadScope | null;
357
387
  contextNamespace?: string;
358
388
  onThreadRestoreNotFound?: () => void;
@@ -403,6 +433,10 @@ vi.mock("./AgentKitAssistantChat.js", async () => {
403
433
  }
404
434
  data-disabled-placeholder={props.composerDisabledPlaceholder}
405
435
  data-composer-active={props.isActiveComposer ? "true" : "false"}
436
+ data-new-thread={props.isNewThread ? "true" : "false"}
437
+ data-thread-state-loading={
438
+ props.isThreadStateLoading ? "true" : "false"
439
+ }
406
440
  data-context-scope={
407
441
  props.contextScope
408
442
  ? `${props.contextScope.type}:${props.contextScope.id}`
@@ -427,6 +461,7 @@ function resetThreadMocks() {
427
461
  assistantChatMockState.onSaveThread = undefined;
428
462
  assistantChatMockState.branchNavigation = undefined;
429
463
  threadMocks.activeThreadId = "thread-1";
464
+ threadMocks.isLoading = false;
430
465
  threadMocks.evictedThreadIds = [];
431
466
  threadMocks.threads = [
432
467
  {
@@ -1024,6 +1059,51 @@ describe("MultiTabAssistantChat postMessage bridge", () => {
1024
1059
  await view.cleanup();
1025
1060
  });
1026
1061
 
1062
+ it("reports the active thread's exact engine to its resource panel", async () => {
1063
+ const storageKey = "resources-engine-context";
1064
+ const engines = [
1065
+ {
1066
+ name: "anthropic",
1067
+ label: "Anthropic",
1068
+ defaultModel: "claude-sonnet-5-5",
1069
+ supportedModels: ["claude-sonnet-5-5", "claude-fable-5"],
1070
+ requiredEnvVars: ["ANTHROPIC_API_KEY"],
1071
+ },
1072
+ ];
1073
+ stubCatalog(engines, ["ANTHROPIC_API_KEY"]);
1074
+ window.localStorage.setItem(
1075
+ chatModelSelectionStorageKey(storageKey),
1076
+ JSON.stringify({ model: "claude-sonnet-5-5", engine: "anthropic" }),
1077
+ );
1078
+
1079
+ let selectedEngine: {
1080
+ name: string;
1081
+ supportedModels: readonly string[];
1082
+ } | null = null;
1083
+ const el = document.createElement("div");
1084
+ document.body.appendChild(el);
1085
+ const localRoot = createRoot(el);
1086
+ await act(async () => {
1087
+ localRoot.render(
1088
+ <MultiTabAssistantChat
1089
+ storageKey={storageKey}
1090
+ onActiveModelEngineChange={(engine) => {
1091
+ selectedEngine = engine;
1092
+ }}
1093
+ />,
1094
+ );
1095
+ await Promise.resolve();
1096
+ await Promise.resolve();
1097
+ });
1098
+
1099
+ expect(selectedEngine).toMatchObject({
1100
+ name: "anthropic",
1101
+ supportedModels: ["claude-sonnet-5-5", "claude-fable-5"],
1102
+ });
1103
+ await act(async () => localRoot.unmount());
1104
+ el.remove();
1105
+ });
1106
+
1027
1107
  it("keeps the last model readiness when status refresh is unavailable", async () => {
1028
1108
  const view = await mountWithCatalog(ANTHROPIC_ENGINES, [
1029
1109
  "ANTHROPIC_API_KEY",
@@ -1049,25 +1129,58 @@ describe("MultiTabAssistantChat postMessage bridge", () => {
1049
1129
 
1050
1130
  it("keeps a host-supplied model catalog instead of the discovered one", async () => {
1051
1131
  stubCatalog(ANTHROPIC_ENGINES, ["ANTHROPIC_API_KEY"]);
1132
+ const storageKey = "host-catalog-test";
1133
+ window.localStorage.setItem(
1134
+ chatModelSelectionStorageKey(storageKey),
1135
+ JSON.stringify({ model: "host-model", engine: "anthropic" }),
1136
+ );
1137
+ let activeEngine: {
1138
+ name: string;
1139
+ defaultModel: string;
1140
+ supportedModels: readonly string[];
1141
+ selectableModels?: readonly string[];
1142
+ } | null = null;
1052
1143
  const el = document.createElement("div");
1053
1144
  document.body.appendChild(el);
1054
1145
  const localRoot = createRoot(el);
1055
1146
  await act(async () => {
1056
1147
  localRoot.render(
1057
1148
  <MultiTabAssistantChat
1058
- storageKey="host-catalog-test"
1149
+ storageKey={storageKey}
1150
+ onActiveModelEngineChange={(engine) => {
1151
+ activeEngine = engine;
1152
+ }}
1153
+ />,
1154
+ );
1155
+ await Promise.resolve();
1156
+ await Promise.resolve();
1157
+ });
1158
+ await act(async () => {
1159
+ await Promise.resolve();
1160
+ await Promise.resolve();
1161
+ });
1162
+ expect(activeEngine).toMatchObject({
1163
+ name: "anthropic",
1164
+ supportedModels: ["claude-sonnet-5"],
1165
+ });
1166
+
1167
+ await act(async () => {
1168
+ localRoot.render(
1169
+ <MultiTabAssistantChat
1170
+ storageKey={storageKey}
1059
1171
  availableModels={[
1060
1172
  {
1061
- engine: "host",
1062
- label: "Host",
1063
- models: ["host-model"],
1173
+ engine: "anthropic",
1174
+ label: "Host Anthropic",
1175
+ models: ["host-model", "host-model-2"],
1064
1176
  configured: true,
1065
1177
  },
1066
1178
  ]}
1179
+ onActiveModelEngineChange={(engine) => {
1180
+ activeEngine = engine;
1181
+ }}
1067
1182
  />,
1068
1183
  );
1069
- });
1070
- await act(async () => {
1071
1184
  await Promise.resolve();
1072
1185
  await Promise.resolve();
1073
1186
  });
@@ -1076,7 +1189,13 @@ describe("MultiTabAssistantChat postMessage bridge", () => {
1076
1189
  el
1077
1190
  .querySelector("[data-testid='assistant-chat']")
1078
1191
  ?.getAttribute("data-model-catalog"),
1079
- ).toBe("host:true");
1192
+ ).toBe("anthropic:true");
1193
+ expect(activeEngine).toMatchObject({
1194
+ name: "anthropic",
1195
+ defaultModel: "host-model",
1196
+ supportedModels: ["host-model", "host-model-2"],
1197
+ selectableModels: ["host-model", "host-model-2"],
1198
+ });
1080
1199
 
1081
1200
  await act(async () => localRoot.unmount());
1082
1201
  el.remove();
@@ -1469,6 +1588,32 @@ describe("MultiTabAssistantChat postMessage bridge", () => {
1469
1588
  window.removeEventListener("agentNative.chatSubmitTarget", onTarget);
1470
1589
  });
1471
1590
 
1591
+ it("shows a known-new chat while the separate thread list is loading", () => {
1592
+ threadMocks.isLoading = true;
1593
+ threadMocks.isNewThread.mockReturnValue(true);
1594
+
1595
+ act(() => {
1596
+ root.render(<MultiTabAssistantChat storageKey="bridge-test" />);
1597
+ });
1598
+
1599
+ const chat = container.querySelector("[data-testid='assistant-chat']");
1600
+ expect(chat?.getAttribute("data-new-thread")).toBe("true");
1601
+ expect(chat?.getAttribute("data-thread-state-loading")).toBe("false");
1602
+ });
1603
+
1604
+ it("keeps existing thread restoration loading while the thread list loads", () => {
1605
+ threadMocks.isLoading = true;
1606
+ threadMocks.isNewThread.mockReturnValue(false);
1607
+
1608
+ act(() => {
1609
+ root.render(<MultiTabAssistantChat storageKey="bridge-test" />);
1610
+ });
1611
+
1612
+ const chat = container.querySelector("[data-testid='assistant-chat']");
1613
+ expect(chat?.getAttribute("data-new-thread")).toBe("false");
1614
+ expect(chat?.getAttribute("data-thread-state-loading")).toBe("true");
1615
+ });
1616
+
1472
1617
  it("creates a foreground tab when the active chat has messages", async () => {
1473
1618
  threadMocks.isNewThread.mockReturnValue(true);
1474
1619
  chatHandleMocks.exportThreadSnapshot.mockReturnValueOnce({
@@ -1,5 +1,6 @@
1
1
  import type { AgentChatAttachment } from "@agent-native/core";
2
2
  import { CHATGPT_SUBSCRIPTION_ENGINE_NAME } from "@agent-native/core/agent/chatgpt-subscription-contract";
3
+ import type { ModelEngineConfig } from "@agent-native/core/agent/model-version";
3
4
  import type { AgentChatMessage } from "@agent-native/core/client/agent-chat";
4
5
  import {
5
6
  DEFAULT_MODEL,
@@ -885,6 +886,8 @@ export type MultiTabAssistantChatProps = Omit<
885
886
  showScopeBadge?: boolean;
886
887
  /** Cadence for hydrating agent-team sub-agent tab status. Default: 3000. */
887
888
  agentTeamPollMs?: number;
889
+ /** Reports the exact model engine selected for the active thread. */
890
+ onActiveModelEngineChange?: (engine: ModelEngineConfig | null) => void;
888
891
  };
889
892
 
890
893
  export function MultiTabAssistantChat({
@@ -905,6 +908,7 @@ export function MultiTabAssistantChat({
905
908
  modelListError: hostModelListError,
906
909
  onRetryModelList: hostOnRetryModelList,
907
910
  onModelChange: hostOnModelChange,
911
+ onActiveModelEngineChange,
908
912
  ...props
909
913
  }: MultiTabAssistantChatProps) {
910
914
  const translate = useT();
@@ -1154,6 +1158,9 @@ export function MultiTabAssistantChat({
1154
1158
  const [discoveredModels, setDiscoveredModels] = useState<EngineModelGroup[]>(
1155
1159
  [],
1156
1160
  );
1161
+ const [discoveredModelEngines, setDiscoveredModelEngines] = useState<
1162
+ Readonly<Record<string, ModelEngineConfig>>
1163
+ >({});
1157
1164
  const availableModels = hostAvailableModels ?? discoveredModels;
1158
1165
  const [discoveredModelsLoading, setModelListLoading] = useState(true);
1159
1166
  const [discoveredModelsError, setDiscoveredModelsError] = useState(false);
@@ -1280,6 +1287,51 @@ export function MultiTabAssistantChat({
1280
1287
  [availableModels, persistedModelSelection, modelSelectionVersion],
1281
1288
  );
1282
1289
 
1290
+ useEffect(() => {
1291
+ if (!onActiveModelEngineChange) return;
1292
+ const selection = activeThreadId
1293
+ ? resolveThreadModelSelection(activeThreadId)
1294
+ : undefined;
1295
+ const selectedModel = selection?.model ?? defaultModel;
1296
+ const engineName =
1297
+ selection?.engine ??
1298
+ availableModels.find((group) => group.models.includes(selectedModel))
1299
+ ?.engine;
1300
+ const hostEngineGroups = hostManagedModels
1301
+ ? availableModels.filter((group) => group.engine === engineName)
1302
+ : [];
1303
+ const hostSelectableModels = [
1304
+ ...new Set(hostEngineGroups.flatMap((group) => group.models)),
1305
+ ];
1306
+ const hostModelEngine =
1307
+ engineName && hostSelectableModels.length > 0
1308
+ ? {
1309
+ name: engineName,
1310
+ label: hostEngineGroups[0]?.label ?? engineName,
1311
+ defaultModel: hostSelectableModels.includes(selectedModel)
1312
+ ? selectedModel
1313
+ : hostSelectableModels[0]!,
1314
+ supportedModels: hostSelectableModels,
1315
+ selectableModels: hostSelectableModels,
1316
+ }
1317
+ : null;
1318
+ onActiveModelEngineChange(
1319
+ hostManagedModels
1320
+ ? hostModelEngine
1321
+ : engineName
1322
+ ? (discoveredModelEngines[engineName] ?? null)
1323
+ : null,
1324
+ );
1325
+ }, [
1326
+ activeThreadId,
1327
+ availableModels,
1328
+ defaultModel,
1329
+ discoveredModelEngines,
1330
+ hostManagedModels,
1331
+ onActiveModelEngineChange,
1332
+ resolveThreadModelSelection,
1333
+ ]);
1334
+
1283
1335
  const persistModelSelection = useCallback(
1284
1336
  (selection: ModelSelection) => {
1285
1337
  setPersistedModelSelection(selection);
@@ -1425,10 +1477,25 @@ export function MultiTabAssistantChat({
1425
1477
  }
1426
1478
  setDiscoveredModelsError(false);
1427
1479
  setDiscoveredModels(catalog.groups);
1480
+ setDiscoveredModelEngines(catalog.modelEngines);
1428
1481
  setDefaultModel(catalog.defaultModel);
1429
1482
  void catalog.loadLiveGroups().then((liveGroups) => {
1430
1483
  if (isCurrentRequest() && liveGroups) {
1431
1484
  setDiscoveredModels(liveGroups);
1485
+ setDiscoveredModelEngines((current) => {
1486
+ const next = { ...current };
1487
+ for (const group of liveGroups) {
1488
+ const engine = next[group.engine];
1489
+ if (engine) {
1490
+ next[group.engine] = {
1491
+ ...engine,
1492
+ supportedModels: group.models,
1493
+ selectableModels: group.models,
1494
+ };
1495
+ }
1496
+ }
1497
+ return next;
1498
+ });
1432
1499
  }
1433
1500
  });
1434
1501
  })
@@ -3194,6 +3261,8 @@ export function MultiTabAssistantChat({
3194
3261
  tabId === activeThreadId || mountedTabsRef.current.has(tabId),
3195
3262
  )
3196
3263
  .map((tabId) => {
3264
+ const isKnownNewThread =
3265
+ newThreadIds.current.has(tabId) || isNewThread(tabId);
3197
3266
  const modelSelection = resolveThreadModelSelection(tabId);
3198
3267
  const modelSelectionPending =
3199
3268
  !hostManagedModels && modelListLoading && !modelSelection;
@@ -3229,9 +3298,7 @@ export function MultiTabAssistantChat({
3229
3298
  isolateHistoryByScope={isolateHistoryByScope}
3230
3299
  isActiveComposer={!contentHidden && tabId === activeThreadId}
3231
3300
  apiUrl={apiUrl}
3232
- isNewThread={
3233
- newThreadIds.current.has(tabId) || isNewThread(tabId)
3234
- }
3301
+ isNewThread={isKnownNewThread}
3235
3302
  onThreadRestoreNotFound={
3236
3303
  tabId === activeThreadId &&
3237
3304
  (props.agentChatSurface !== "desktop" ||
@@ -3239,7 +3306,7 @@ export function MultiTabAssistantChat({
3239
3306
  ? clearActiveTab
3240
3307
  : undefined
3241
3308
  }
3242
- isThreadStateLoading={isLoading}
3309
+ isThreadStateLoading={isLoading && !isKnownNewThread}
3243
3310
  onMessageCountChange={(count) => {
3244
3311
  setMessageCounts((prev) =>
3245
3312
  prev[tabId] === count
@@ -126,6 +126,23 @@ describe("CoreAgentKitApproval", () => {
126
126
  expect(startRun).not.toHaveBeenCalled();
127
127
  });
128
128
 
129
+ it("masks a failed resolution's message in replays", async () => {
130
+ const client = new AgentKitClient({
131
+ transport: createTransport({
132
+ resolveApproval: vi.fn(async () => {
133
+ throw new Error("Jane Doe's calendar is locked");
134
+ }),
135
+ }),
136
+ });
137
+ renderApproval(client);
138
+
139
+ await clickButton("Approve");
140
+
141
+ const error = container.querySelector(".agentkit-command-error");
142
+ expect(error?.textContent).toBe("Jane Doe's calendar is locked");
143
+ expect(error?.hasAttribute("data-an-mask")).toBe(true);
144
+ });
145
+
129
146
  it("uses a generic summary when the tool name is not safe to display", () => {
130
147
  const client = new AgentKitClient({ transport: createTransport() });
131
148
  renderApproval(client, {
@@ -12,6 +12,7 @@ import {
12
12
  } from "../../agentkit/react/context.js";
13
13
  import { ActionCard } from "../chat/widgets/ActionCard.js";
14
14
  import { compactOutlineButtonClassName } from "../components/ui/button-classes.js";
15
+ import { SESSION_REPLAY_MASK_PROPS } from "../session-replay-privacy.js";
15
16
 
16
17
  type ApprovalSlotProps = AgentKitRenderProps<AgentApprovalRequest> & {
17
18
  runId: string;
@@ -160,6 +161,7 @@ function SimpleToolApproval({
160
161
  />
161
162
  {resolution.error ? (
162
163
  <p
164
+ {...SESSION_REPLAY_MASK_PROPS}
163
165
  role="alert"
164
166
  className="agentkit-command-error block px-3 pb-3 text-xs"
165
167
  >