@agent-native/toolkit 0.207.0-nightly-20261011124220 → 0.207.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/agent-native.eject.json +1 -0
  2. package/dist/app/auth/AuthPage.d.ts.map +1 -1
  3. package/dist/app/auth/AuthPage.js +24 -55
  4. package/dist/app/auth/AuthPage.js.map +1 -1
  5. package/dist/app/chat/chat/run-recovery.d.ts.map +1 -1
  6. package/dist/app/chat/chat/run-recovery.js +21 -5
  7. package/dist/app/chat/chat/run-recovery.js.map +1 -1
  8. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.d.ts.map +1 -1
  9. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.js +3 -2
  10. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.js.map +1 -1
  11. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  12. package/dist/app/i18n/catalogs/ar-SA.js +11 -3
  13. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  14. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  15. package/dist/app/i18n/catalogs/de-DE.js +11 -3
  16. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  17. package/dist/app/i18n/catalogs/en-US.d.ts +11 -3
  18. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  19. package/dist/app/i18n/catalogs/en-US.js +11 -3
  20. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  21. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  22. package/dist/app/i18n/catalogs/es-ES.js +11 -3
  23. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  24. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  25. package/dist/app/i18n/catalogs/fr-FR.js +11 -3
  26. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  27. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  28. package/dist/app/i18n/catalogs/hi-IN.js +11 -3
  29. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  30. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  31. package/dist/app/i18n/catalogs/ja-JP.js +11 -3
  32. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  33. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  34. package/dist/app/i18n/catalogs/ko-KR.js +11 -3
  35. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  36. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  37. package/dist/app/i18n/catalogs/pt-BR.js +11 -3
  38. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  39. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  40. package/dist/app/i18n/catalogs/zh-CN.js +11 -3
  41. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  42. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  43. package/dist/app/i18n/catalogs/zh-TW.js +11 -3
  44. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  45. package/dist/app/onboarding/FirstRunOnboarding.d.ts +2 -3
  46. package/dist/app/onboarding/FirstRunOnboarding.d.ts.map +1 -1
  47. package/dist/app/onboarding/FirstRunOnboarding.js +3 -4
  48. package/dist/app/onboarding/FirstRunOnboarding.js.map +1 -1
  49. package/dist/app/onboarding/OnboardingBanner.d.ts.map +1 -1
  50. package/dist/app/onboarding/OnboardingBanner.js +37 -3
  51. package/dist/app/onboarding/OnboardingBanner.js.map +1 -1
  52. package/dist/app/onboarding/OnboardingPanel.d.ts.map +1 -1
  53. package/dist/app/onboarding/OnboardingPanel.js +49 -11
  54. package/dist/app/onboarding/OnboardingPanel.js.map +1 -1
  55. package/dist/app/progress/RunsTray.d.ts.map +1 -1
  56. package/dist/app/progress/RunsTray.js +136 -17
  57. package/dist/app/progress/RunsTray.js.map +1 -1
  58. package/dist/app/settings/BuilderConnectPopover.d.ts +1 -0
  59. package/dist/app/settings/BuilderConnectPopover.d.ts.map +1 -1
  60. package/dist/app/settings/BuilderConnectPopover.js +21 -5
  61. package/dist/app/settings/BuilderConnectPopover.js.map +1 -1
  62. package/dist/app/settings/shell/pages/audit.d.ts.map +1 -1
  63. package/dist/app/settings/shell/pages/audit.js +3 -3
  64. package/dist/app/settings/shell/pages/audit.js.map +1 -1
  65. package/dist/app/settings/useBuilderStatus.d.ts +1 -0
  66. package/dist/app/settings/useBuilderStatus.d.ts.map +1 -1
  67. package/dist/app/settings/useBuilderStatus.js +1 -0
  68. package/dist/app/settings/useBuilderStatus.js.map +1 -1
  69. package/dist/app/shared/AgentMark.d.ts +6 -0
  70. package/dist/app/shared/AgentMark.d.ts.map +1 -0
  71. package/dist/app/shared/AgentMark.js +5 -0
  72. package/dist/app/shared/AgentMark.js.map +1 -0
  73. package/dist/app/shared/index.d.ts +1 -0
  74. package/dist/app/shared/index.d.ts.map +1 -1
  75. package/dist/app/shared/index.js +1 -0
  76. package/dist/app/shared/index.js.map +1 -1
  77. package/dist/app/sharing/ShareButton.d.ts +1 -0
  78. package/dist/app/sharing/ShareButton.d.ts.map +1 -1
  79. package/dist/app/sharing/ShareButton.js +2 -2
  80. package/dist/app/sharing/ShareButton.js.map +1 -1
  81. package/dist/app/styles/agent-native.css +1 -0
  82. package/dist/app-shell/sidebar.d.ts.map +1 -1
  83. package/dist/app-shell/sidebar.js +3 -3
  84. package/dist/app-shell/sidebar.js.map +1 -1
  85. package/dist/sharing/ShareControls.d.ts +1 -0
  86. package/dist/sharing/ShareControls.d.ts.map +1 -1
  87. package/dist/sharing/ShareControls.js +5 -2
  88. package/dist/sharing/ShareControls.js.map +1 -1
  89. package/package.json +4 -4
  90. package/src/app/auth/AuthPage.attribution.spec.tsx +268 -0
  91. package/src/app/auth/AuthPage.tsx +27 -54
  92. package/src/app/chat/chat/run-recovery.spec.tsx +76 -0
  93. package/src/app/chat/chat/run-recovery.tsx +101 -23
  94. package/src/app/chat/chat-first/ChatFirstAgentActivityPanel.tsx +11 -5
  95. package/src/app/i18n/catalogs/ar-SA.ts +18 -3
  96. package/src/app/i18n/catalogs/de-DE.ts +19 -3
  97. package/src/app/i18n/catalogs/en-US.ts +18 -3
  98. package/src/app/i18n/catalogs/es-ES.ts +18 -3
  99. package/src/app/i18n/catalogs/fr-FR.ts +19 -3
  100. package/src/app/i18n/catalogs/hi-IN.ts +18 -3
  101. package/src/app/i18n/catalogs/ja-JP.ts +18 -3
  102. package/src/app/i18n/catalogs/ko-KR.ts +18 -3
  103. package/src/app/i18n/catalogs/pt-BR.ts +18 -3
  104. package/src/app/i18n/catalogs/zh-CN.ts +17 -3
  105. package/src/app/i18n/catalogs/zh-TW.ts +17 -3
  106. package/src/app/onboarding/FirstRunOnboarding.spec.tsx +49 -1
  107. package/src/app/onboarding/FirstRunOnboarding.tsx +4 -13
  108. package/src/app/onboarding/OnboardingBanner.tsx +70 -5
  109. package/src/app/onboarding/OnboardingPanel.save-scope.spec.tsx +143 -2
  110. package/src/app/onboarding/OnboardingPanel.tsx +72 -5
  111. package/src/app/progress/RunsTray.spec.tsx +577 -0
  112. package/src/app/progress/RunsTray.tsx +253 -51
  113. package/src/app/settings/BuilderConnectPopover.spec.tsx +39 -1
  114. package/src/app/settings/BuilderConnectPopover.tsx +27 -4
  115. package/src/app/settings/shell/pages/audit.tsx +7 -3
  116. package/src/app/settings/useBuilderStatus.spec.tsx +21 -1
  117. package/src/app/settings/useBuilderStatus.ts +2 -0
  118. package/src/app/shared/AgentMark.tsx +34 -0
  119. package/src/app/shared/index.ts +1 -0
  120. package/src/app/sharing/ShareButton.tsx +3 -0
  121. package/src/app/styles/agent-native.css +1 -0
  122. package/src/app-shell/sidebar.tsx +29 -21
  123. package/src/sharing/ShareControls.spec.tsx +36 -0
  124. package/src/sharing/ShareControls.tsx +17 -2
@@ -132,7 +132,7 @@ const messages: ToolkitAgentChatTranslation = {
132
132
  "onboarding.roleOtherInputLabel": "Descreva sua função",
133
133
  "onboarding.skipForNow": "Pular por enquanto",
134
134
  "onboarding.saveRoleError": "Não foi possível salvar sua função.",
135
- "onboarding.builderCreateAccount": "Criar uma conta do Builder.io",
135
+ "onboarding.builderCreateAccount": "Usar o Builder.io",
136
136
  "onboarding.builderSignInWithAccount": "Entrar com uma conta do Builder.io",
137
137
  "onboarding.builderActivateDescription":
138
138
  "Crie ou reutilize sua conta do Builder.io e ative os créditos gratuitos com um clique.",
@@ -153,8 +153,17 @@ const messages: ToolkitAgentChatTranslation = {
153
153
  "onboarding.builderAccountExistsTitle": "Você já tem uma conta do Builder.io",
154
154
  "onboarding.builderAccountExistsDescription":
155
155
  "Faça login para usar sua conta.",
156
+ "onboarding.builderActivationChecking":
157
+ "Verificando se a criação de uma nova conta está disponível.",
158
+ "onboarding.builderActivationUnavailable":
159
+ "A criação de novas contas não está ativada neste app. Você ainda pode conectar uma conta existente.",
160
+ "onboarding.builderActivationUnavailableInMode":
161
+ "A ativação de uma nova conta não está disponível neste modo. Você ainda pode conectar uma conta existente.",
162
+ "onboarding.builderActivationCheckFailed":
163
+ "Não foi possível verificar se este app pode criar novas contas.",
164
+
156
165
  "onboarding.builderActivationDescription":
157
- "Crie ou conecte uma conta do Builder.io com um clique para obter créditos grátis.",
166
+ "Crie uma nova conta para ativar créditos grátis ou continue com uma conta existente.",
158
167
  "onboarding.builderOrgActivationDescription":
159
168
  "Criaremos sua conta do Builder.io com um clique para que sua organização possa usá-la.",
160
169
  "onboarding.builderCreateAndActivate": "Criar e ativar",
@@ -163,7 +172,7 @@ const messages: ToolkitAgentChatTranslation = {
163
172
  "onboarding.builderTerms": "Termos de Serviço",
164
173
  "onboarding.builderPrivacy": "Política de Privacidade",
165
174
  "onboarding.builderConsentAnd": "e",
166
- "onboarding.builderExistingAccount": "Usar Builder.io",
175
+ "onboarding.builderExistingAccount": "Tenho uma conta Builder.io",
167
176
  "onboarding.builderActivating":
168
177
  "Ativando os créditos gratuitos do Builder.io",
169
178
  "onboarding.builderConnecting":
@@ -893,6 +902,12 @@ const messages: ToolkitAgentChatTranslation = {
893
902
  "secrets.usePersonalKey": "Usar uma chave pessoal",
894
903
  "selection.attached": "{{formattedCount}} caracteres da seleção anexados",
895
904
  "selection.clear": "Limpar contexto da seleção",
905
+ "setup.addKeyInSettings": "Adicionar uma chave nas Configurações",
906
+ "setup.addKeyInSettingsDescription": "Para sua conta ou organização.",
907
+ "setup.setEnvironmentVariable": "Definir uma variável de ambiente",
908
+ "setup.setEnvironmentVariableDescription":
909
+ "Uma chave de provedor para todos neste app.",
910
+
896
911
  "setup.addOwnKeys": "Adicionar suas próprias chaves",
897
912
  "setup.builderCredits":
898
913
  "O Builder.io inclui créditos gratuitos, ou você pode usar sua própria chave de API.",
@@ -126,7 +126,7 @@ const messages: ToolkitAgentChatTranslation = {
126
126
  "onboarding.roleOtherInputLabel": "描述你的角色",
127
127
  "onboarding.skipForNow": "暂时跳过",
128
128
  "onboarding.saveRoleError": "无法保存你的角色。",
129
- "onboarding.builderCreateAccount": "创建 Builder.io 账户",
129
+ "onboarding.builderCreateAccount": "使用 Builder.io",
130
130
  "onboarding.builderSignInWithAccount": "使用 Builder.io 账户登录",
131
131
  "onboarding.builderActivateDescription":
132
132
  "一键创建或重新使用您的 Builder.io 账户,并激活免费额度。",
@@ -145,8 +145,16 @@ const messages: ToolkitAgentChatTranslation = {
145
145
  "onboarding.builderActivateTitle": "激活免费额度",
146
146
  "onboarding.builderAccountExistsTitle": "您已有 Builder.io 账户",
147
147
  "onboarding.builderAccountExistsDescription": "登录以使用你的账户。",
148
+ "onboarding.builderActivationChecking": "正在检查是否可以创建新账户。",
149
+ "onboarding.builderActivationUnavailable":
150
+ "此应用未启用新账户创建功能。你仍可连接现有账户。",
151
+ "onboarding.builderActivationUnavailableInMode":
152
+ "此模式下无法激活新账户。你仍然可以连接现有账户。",
153
+ "onboarding.builderActivationCheckFailed":
154
+ "无法确认此应用是否可以创建新账户。",
155
+
148
156
  "onboarding.builderActivationDescription":
149
- "一键创建或连接 Builder.io 账户,即可获得免费额度。",
157
+ "创建新账户以激活免费额度,或使用现有账户继续。",
150
158
  "onboarding.builderOrgActivationDescription":
151
159
  "我们会一键创建你的 Builder.io 账户,让你的组织可以使用它。",
152
160
  "onboarding.builderCreateAndActivate": "创建并激活",
@@ -154,7 +162,7 @@ const messages: ToolkitAgentChatTranslation = {
154
162
  "onboarding.builderTerms": "服务条款",
155
163
  "onboarding.builderPrivacy": "隐私政策",
156
164
  "onboarding.builderConsentAnd": "和",
157
- "onboarding.builderExistingAccount": "使用 Builder.io",
165
+ "onboarding.builderExistingAccount": "我有 Builder.io 账户",
158
166
  "onboarding.builderActivating": "正在激活 Builder.io 免费额度",
159
167
  "onboarding.builderConnecting": "正在设置 Builder.io 免费额度",
160
168
  "onboarding.builderProvisioningDescription":
@@ -1157,6 +1165,12 @@ const messages: ToolkitAgentChatTranslation = {
1157
1165
  "selection.attached": "已附加选中内容的 {{formattedCount}} 个字符",
1158
1166
  "selection.attached_other": "已附加选中内容的 {{formattedCount}} 个字符",
1159
1167
  "selection.clear": "清除选中内容上下文",
1168
+ "setup.addKeyInSettings": "在设置中添加密钥",
1169
+ "setup.addKeyInSettingsDescription": "用于你的个人账户或组织。",
1170
+ "setup.setEnvironmentVariable": "设置环境变量",
1171
+ "setup.setEnvironmentVariableDescription":
1172
+ "此应用的所有用户共用一个提供商密钥。",
1173
+
1160
1174
  "setup.addOwnKeys": "添加您自己的密钥",
1161
1175
  "setup.builderCredits":
1162
1176
  "Builder.io 提供免费额度,您也可以使用自己的 API 密钥。",
@@ -127,7 +127,7 @@ const messages: ToolkitAgentChatTranslation = {
127
127
  "onboarding.roleOtherInputLabel": "描述你的角色",
128
128
  "onboarding.skipForNow": "暫時略過",
129
129
  "onboarding.saveRoleError": "無法儲存你的角色。",
130
- "onboarding.builderCreateAccount": "建立 Builder.io 帳戶",
130
+ "onboarding.builderCreateAccount": "使用 Builder.io",
131
131
  "onboarding.builderSignInWithAccount": "使用 Builder.io 帳戶登入",
132
132
  "onboarding.builderActivateDescription":
133
133
  "只要按一下即可建立或重新使用您的 Builder.io 帳戶,並啟用免費額度。",
@@ -146,8 +146,16 @@ const messages: ToolkitAgentChatTranslation = {
146
146
  "onboarding.builderActivateTitle": "啟用免費額度",
147
147
  "onboarding.builderAccountExistsTitle": "您已有 Builder.io 帳戶",
148
148
  "onboarding.builderAccountExistsDescription": "登入以使用您的帳戶。",
149
+ "onboarding.builderActivationChecking": "正在確認是否可以建立新帳戶。",
150
+ "onboarding.builderActivationUnavailable":
151
+ "此應用程式未啟用新帳戶建立功能。你仍可連結現有帳戶。",
152
+ "onboarding.builderActivationUnavailableInMode":
153
+ "此模式無法啟用新帳戶。你仍可連結現有帳戶。",
154
+ "onboarding.builderActivationCheckFailed":
155
+ "無法確認此應用程式是否可以建立新帳戶。",
156
+
149
157
  "onboarding.builderActivationDescription":
150
- "按一下即可建立或連接 Builder.io 帳戶,取得免費額度。",
158
+ "建立新帳戶以啟用免費點數,或使用現有帳戶繼續。",
151
159
  "onboarding.builderOrgActivationDescription":
152
160
  "我們會一鍵建立您的 Builder.io 帳戶,讓您的組織可以使用。",
153
161
  "onboarding.builderCreateAndActivate": "建立並啟用",
@@ -155,7 +163,7 @@ const messages: ToolkitAgentChatTranslation = {
155
163
  "onboarding.builderTerms": "服務條款",
156
164
  "onboarding.builderPrivacy": "隱私權政策",
157
165
  "onboarding.builderConsentAnd": "和",
158
- "onboarding.builderExistingAccount": "使用 Builder.io",
166
+ "onboarding.builderExistingAccount": "我有 Builder.io 帳戶",
159
167
  "onboarding.builderActivating": "正在啟用 Builder.io 免費額度",
160
168
  "onboarding.builderConnecting": "正在設定 Builder.io 免費額度",
161
169
  "onboarding.builderProvisioningDescription":
@@ -1161,6 +1169,12 @@ const messages: ToolkitAgentChatTranslation = {
1161
1169
  "selection.attached": "已附加所選內容的 {{formattedCount}} 個字元",
1162
1170
  "selection.attached_other": "已附加所選內容的 {{formattedCount}} 個字元",
1163
1171
  "selection.clear": "清除所選內容的上下文",
1172
+ "setup.addKeyInSettings": "在設定中新增金鑰",
1173
+ "setup.addKeyInSettingsDescription": "供你的個人帳戶或組織使用。",
1174
+ "setup.setEnvironmentVariable": "設定環境變數",
1175
+ "setup.setEnvironmentVariableDescription":
1176
+ "此應用程式的所有使用者共用一個供應商金鑰。",
1177
+
1164
1178
  "setup.addOwnKeys": "加入您自己的金鑰",
1165
1179
  "setup.builderCredits":
1166
1180
  "Builder.io 提供免費額度,您也可以使用自己的 API 金鑰。",
@@ -991,7 +991,7 @@ describe("FirstRunOnboarding", () => {
991
991
  document.body.querySelector(
992
992
  '[data-testid="first-run-builder-create-account"]',
993
993
  )?.textContent,
994
- ).toBe("Create a Builder.io account");
994
+ ).toBe("Use Builder.io");
995
995
  expect(
996
996
  document.body.querySelector('[data-testid="first-run-builder-sign-in"]')
997
997
  ?.textContent,
@@ -1567,6 +1567,54 @@ describe("FirstRunOnboarding", () => {
1567
1567
  expect(document.body.textContent).not.toContain("This app is an agent.");
1568
1568
  });
1569
1569
 
1570
+ it("labels Clips setup as Builder, custom keys, or an optional skip", async () => {
1571
+ mocks.useOnboarding.mockReturnValue({
1572
+ firstRun: true,
1573
+ loading: false,
1574
+ error: null,
1575
+ profile: { appId: "clips", appName: "Clips", capabilities: [] },
1576
+ completeFirstRun: mocks.completeFirstRun,
1577
+ completeFirstRunError: null,
1578
+ });
1579
+
1580
+ await act(async () => {
1581
+ root.render(
1582
+ <TooltipProvider>
1583
+ <FirstRunOnboarding />
1584
+ </TooltipProvider>,
1585
+ );
1586
+ await Promise.resolve();
1587
+ });
1588
+
1589
+ await act(async () => {
1590
+ document.body
1591
+ .querySelector('[data-testid="first-run-role-skip"]')
1592
+ ?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
1593
+ await Promise.resolve();
1594
+ });
1595
+
1596
+ const builderButton = document.body.querySelector<HTMLButtonElement>(
1597
+ '[data-testid="first-run-builder-create-account"]',
1598
+ );
1599
+ const customKeysButton = document.body.querySelector<HTMLButtonElement>(
1600
+ '[data-testid="first-run-open-key-settings"]',
1601
+ );
1602
+ const customKeysCard = customKeysButton?.closest("section");
1603
+
1604
+ expect({
1605
+ builder: builderButton?.textContent,
1606
+ customKeys: customKeysCard?.querySelector("h2")?.textContent,
1607
+ customKeysAction: customKeysButton?.textContent,
1608
+ skip: document.body.querySelector('[data-testid="first-run-setup-skip"]')
1609
+ ?.textContent,
1610
+ }).toEqual({
1611
+ builder: "Use Builder.io",
1612
+ customKeys: "Custom keys",
1613
+ customKeysAction: "Open AI key settings",
1614
+ skip: "Skip for now",
1615
+ });
1616
+ });
1617
+
1570
1618
  it("opens the app directly after a configured Builder connection", () => {
1571
1619
  mocks.useBuilderConnectFlow.mockReturnValue({
1572
1620
  hasFetchedStatus: true,
@@ -145,9 +145,8 @@ const FIRST_RUN_ROLE_OPTIONS = [
145
145
  ] as const;
146
146
 
147
147
  /**
148
- * Where "Skip and configure manually" lands: Agent › Model, whose empty state
149
- * adds a provider key in one click, since the agent can't answer until a model
150
- * provider is set up. API keys with the redesign off.
148
+ * Where the custom-key choice lands: Agent › Model, whose empty state adds a
149
+ * provider key in one click. API keys with the redesign off.
151
150
  */
152
151
  export function manualSetupSettingsRoute({
153
152
  redesign,
@@ -853,17 +852,9 @@ export function FirstRunOnboarding({
853
852
  <section className="flex flex-col gap-5 rounded-2xl border border-border bg-card p-6">
854
853
  <div className="flex flex-col gap-2">
855
854
  <h2 className="text-lg font-bold text-foreground">
856
- Configure manually
855
+ {t("agentChat.composer.addOwnKeys")}
857
856
  </h2>
858
857
  </div>
859
- <div className="flex flex-col gap-1 rounded-xl bg-muted px-4 py-3">
860
- <p className="text-[13px] font-semibold text-foreground">
861
- You configure and maintain everything
862
- </p>
863
- <p className="text-xs text-muted-foreground">
864
- Full control over your infrastructure
865
- </p>
866
- </div>
867
858
  <CapabilityList
868
859
  capabilities={profile.capabilities}
869
860
  className="flex-1"
@@ -874,7 +865,7 @@ export function FirstRunOnboarding({
874
865
  className={cn(secondaryButtonClass, "w-full")}
875
866
  onClick={() => void handleOpenSettings()}
876
867
  >
877
- Skip and configure manually
868
+ {t("agentChat.onboarding.openAiKeySettings")}
878
869
  </button>
879
870
  </section>
880
871
  </div>
@@ -1,6 +1,12 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
1
2
  import { useOnboarding } from "@agent-native/core/client/onboarding/use-onboarding";
2
- import { IconChecklist, IconChevronRight } from "@tabler/icons-react";
3
- import React from "react";
3
+ import { Button } from "@agent-native/toolkit/ui/button";
4
+ import {
5
+ IconChecklist,
6
+ IconChevronRight,
7
+ IconLoader2,
8
+ } from "@tabler/icons-react";
9
+ import React, { useState } from "react";
4
10
 
5
11
  interface OnboardingBannerProps {
6
12
  onContinue?: () => void;
@@ -11,9 +17,55 @@ export function OnboardingBanner({
11
17
  onContinue,
12
18
  className,
13
19
  }: OnboardingBannerProps) {
14
- const { loading, totalCount, completeCount, allComplete, dismissed } =
15
- useOnboarding();
16
- if (loading || totalCount === 0 || allComplete || dismissed) return null;
20
+ const {
21
+ loading,
22
+ totalCount,
23
+ completeCount,
24
+ allComplete,
25
+ dismissed,
26
+ error,
27
+ summaryKnown,
28
+ refresh,
29
+ } = useOnboarding();
30
+ const t = useT();
31
+ const [retrying, setRetrying] = useState(false);
32
+
33
+ if (
34
+ loading ||
35
+ (summaryKnown && (dismissed || (totalCount > 0 && allComplete)))
36
+ ) {
37
+ return null;
38
+ }
39
+ if (error && (!summaryKnown || totalCount > 0)) {
40
+ const retry = async () => {
41
+ setRetrying(true);
42
+ try {
43
+ await refresh();
44
+ } finally {
45
+ setRetrying(false);
46
+ }
47
+ };
48
+
49
+ return (
50
+ <div role="alert" className={className} style={styles.error}>
51
+ <span>{t("agentChat.common.chunkLoadFailed")}</span>
52
+ <Button
53
+ type="button"
54
+ variant="outline"
55
+ size="xs"
56
+ data-testid="onboarding-summary-retry"
57
+ disabled={retrying}
58
+ onClick={() => void retry()}
59
+ >
60
+ {retrying ? (
61
+ <IconLoader2 className="animate-spin" aria-hidden />
62
+ ) : null}
63
+ {t("agentChat.common.retry")}
64
+ </Button>
65
+ </div>
66
+ );
67
+ }
68
+ if (!summaryKnown || totalCount === 0) return null;
17
69
 
18
70
  return (
19
71
  <button
@@ -38,6 +90,19 @@ export function OnboardingBanner({
38
90
  }
39
91
 
40
92
  const styles: Record<string, React.CSSProperties> = {
93
+ error: {
94
+ display: "flex",
95
+ flexWrap: "wrap",
96
+ alignItems: "center",
97
+ justifyContent: "space-between",
98
+ gap: 8,
99
+ width: "100%",
100
+ padding: "8px 12px",
101
+ borderBottom: "1px solid hsl(var(--border) / 0.7)",
102
+ background: "hsl(var(--muted) / 0.25)",
103
+ color: "hsl(var(--muted-foreground))",
104
+ fontSize: 12,
105
+ },
41
106
  root: {
42
107
  display: "flex",
43
108
  alignItems: "center",
@@ -8,15 +8,26 @@ import { createRoot, type Root } from "react-dom/client";
8
8
  import { afterEach, describe, expect, it, vi } from "vitest";
9
9
 
10
10
  let steps: OnboardingStepStatus[] = [];
11
+ let onboardingError: string | null = null;
12
+ let dismissed = false;
13
+ let summaryKnown = true;
14
+ let refresh = vi.fn(async () => {});
11
15
  vi.mock("@agent-native/core/client/onboarding/use-onboarding", () => ({
12
16
  createOnboardingCorrelationId: () => "test-onboarding-correlation-id",
13
17
  trackOnboardingEvent: vi.fn(),
14
18
  useOnboarding: () => ({
15
19
  steps,
16
20
  currentStepId: steps[0]?.id ?? null,
17
- dismissed: false,
21
+ completeCount: steps.filter((step) => step.complete).length,
22
+ totalCount: steps.length,
23
+ allComplete:
24
+ steps.length > 0 &&
25
+ steps.filter((step) => step.required).every((step) => step.complete),
26
+ dismissed,
27
+ summaryKnown,
18
28
  loading: false,
19
- refresh: async () => {},
29
+ error: onboardingError,
30
+ refresh,
20
31
  complete: vi.fn(),
21
32
  dismiss: vi.fn(),
22
33
  }),
@@ -28,6 +39,7 @@ vi.mock("@agent-native/core/client/use-dev-mode", () => ({
28
39
  useDevMode: () => ({ isDevMode: false }),
29
40
  }));
30
41
 
42
+ import { OnboardingBanner } from "./OnboardingBanner.js";
31
43
  import { OnboardingPanel } from "./OnboardingPanel.js";
32
44
 
33
45
  function json(body: unknown): Response {
@@ -69,9 +81,30 @@ afterEach(() => {
69
81
  container?.remove();
70
82
  root = null;
71
83
  container = null;
84
+ steps = [];
85
+ onboardingError = null;
86
+ dismissed = false;
87
+ summaryKnown = true;
88
+ refresh = vi.fn(async () => {});
72
89
  vi.unstubAllGlobals();
73
90
  });
74
91
 
92
+ async function renderOnboarding(child: React.ReactNode): Promise<void> {
93
+ container = document.createElement("div");
94
+ document.body.appendChild(container);
95
+ root = createRoot(container);
96
+ const queryClient = new QueryClient({
97
+ defaultOptions: { queries: { retry: false } },
98
+ });
99
+ await act(async () => {
100
+ root!.render(
101
+ <QueryClientProvider client={queryClient}>
102
+ <TooltipProvider>{child}</TooltipProvider>
103
+ </QueryClientProvider>,
104
+ );
105
+ });
106
+ }
107
+
75
108
  async function saveAs(
76
109
  role: "member" | "admin",
77
110
  saveTo?: "scoped-secrets",
@@ -156,4 +189,112 @@ describe("onboarding form save scope", () => {
156
189
  const [secretSave] = await saveAs("admin", "scoped-secrets");
157
190
  expect(secretSave.body.scope).toBe("workspace");
158
191
  });
192
+
193
+ it("shows retry only when the initial summary is unavailable", async () => {
194
+ steps = [formStep()];
195
+ onboardingError = "summary: 503";
196
+ summaryKnown = false;
197
+ refresh = vi.fn(async () => {});
198
+ await renderOnboarding(<OnboardingPanel />);
199
+
200
+ const retry = container?.querySelector<HTMLButtonElement>(
201
+ '[data-testid="onboarding-summary-retry"]',
202
+ );
203
+ expect(container?.querySelector('[role="alert"]')).not.toBeNull();
204
+ expect(container?.textContent).not.toContain("Example keys");
205
+ expect(retry).not.toBeNull();
206
+ await act(async () => retry?.click());
207
+ expect(refresh).toHaveBeenCalledOnce();
208
+ });
209
+
210
+ it("keeps known steps visible while showing a summary retry state", async () => {
211
+ steps = [formStep()];
212
+ onboardingError = "summary: 503";
213
+ await renderOnboarding(<OnboardingPanel />);
214
+
215
+ expect(container?.querySelector('[role="alert"]')).not.toBeNull();
216
+ expect(container?.textContent).toContain("Example keys");
217
+ expect(
218
+ container?.querySelector('[data-testid="onboarding-summary-retry"]'),
219
+ ).not.toBeNull();
220
+ });
221
+
222
+ it("shows a retry-only banner when the initial summary is unavailable", async () => {
223
+ steps = [formStep()];
224
+ onboardingError = "summary: 503";
225
+ summaryKnown = false;
226
+ refresh = vi.fn(async () => {});
227
+ await renderOnboarding(<OnboardingBanner />);
228
+
229
+ const retry = container?.querySelector<HTMLButtonElement>(
230
+ '[data-testid="onboarding-summary-retry"]',
231
+ );
232
+ expect(container?.querySelector('[role="alert"]')).not.toBeNull();
233
+ expect(container?.textContent).not.toContain("Example keys");
234
+ expect(container?.textContent).not.toContain("Continue");
235
+ expect(retry).not.toBeNull();
236
+ await act(async () => retry?.click());
237
+ expect(refresh).toHaveBeenCalledOnce();
238
+ });
239
+
240
+ it("shows a retry action on the banner for a known active summary", async () => {
241
+ steps = [formStep()];
242
+ onboardingError = "summary: 503";
243
+ summaryKnown = true;
244
+ refresh = vi.fn(async () => {});
245
+ await renderOnboarding(<OnboardingBanner />);
246
+
247
+ const retry = container?.querySelector<HTMLButtonElement>(
248
+ '[data-testid="onboarding-summary-retry"]',
249
+ );
250
+ expect(container?.querySelector('[role="alert"]')).not.toBeNull();
251
+ expect(retry).not.toBeNull();
252
+ await act(async () => retry?.click());
253
+ expect(refresh).toHaveBeenCalledOnce();
254
+ });
255
+
256
+ it("hides summary errors after onboarding is dismissed", async () => {
257
+ dismissed = true;
258
+ summaryKnown = true;
259
+ onboardingError = "summary: 503";
260
+
261
+ await renderOnboarding(
262
+ <>
263
+ <OnboardingBanner />
264
+ <OnboardingPanel />
265
+ </>,
266
+ );
267
+ expect(container?.querySelector('[role="alert"]')).toBeNull();
268
+ });
269
+
270
+ it("hides summary errors after all required steps are complete", async () => {
271
+ steps = [{ ...formStep(), complete: true }];
272
+ summaryKnown = true;
273
+ onboardingError = "summary: 503";
274
+
275
+ await renderOnboarding(
276
+ <>
277
+ <OnboardingBanner />
278
+ <OnboardingPanel />
279
+ </>,
280
+ );
281
+ expect(container?.querySelector('[role="alert"]')).toBeNull();
282
+ });
283
+
284
+ it("keeps the retry notice visible while the panel is collapsed", async () => {
285
+ steps = [formStep()];
286
+ summaryKnown = true;
287
+ onboardingError = "summary: 503";
288
+ await renderOnboarding(<OnboardingPanel />);
289
+
290
+ const collapse = container?.querySelector<HTMLButtonElement>(
291
+ '[aria-label="Collapse onboarding"]',
292
+ );
293
+ await act(async () => collapse?.click());
294
+
295
+ expect(container?.querySelector('[role="alert"]')).not.toBeNull();
296
+ expect(
297
+ container?.querySelector('[data-testid="onboarding-summary-retry"]'),
298
+ ).not.toBeNull();
299
+ });
159
300
  });
@@ -12,6 +12,7 @@ import type {
12
12
  OnboardingMethod,
13
13
  OnboardingStepStatus,
14
14
  } from "@agent-native/core/onboarding/types";
15
+ import { Button } from "@agent-native/toolkit/ui/button";
15
16
  import {
16
17
  Tooltip,
17
18
  TooltipContent,
@@ -56,10 +57,13 @@ export function OnboardingPanel({
56
57
  currentStepId: rawCurrentStepId,
57
58
  dismissed,
58
59
  loading,
60
+ error,
61
+ summaryKnown,
59
62
  refresh,
60
63
  complete,
61
64
  dismiss,
62
65
  } = onboarding;
66
+ const t = useT();
63
67
  const DEV_ONLY_STEP_IDS = new Set(["database", "auth"]);
64
68
  const steps = isDevMode
65
69
  ? rawSteps
@@ -75,6 +79,7 @@ export function OnboardingPanel({
75
79
  const checklistVisible =
76
80
  !loading && totalCount > 0 && (previewMode || (!dismissed && !allComplete));
77
81
  const [expanded, setExpanded] = useState(true);
82
+ const [retrying, setRetrying] = useState(false);
78
83
  const stepViewRef = useRef<{ key: string; id: string } | null>(null);
79
84
 
80
85
  useEffect(() => {
@@ -102,12 +107,58 @@ export function OnboardingPanel({
102
107
  });
103
108
  }, [checklistVisible, currentStepId, expanded, previewMode, steps]);
104
109
 
105
- if (loading || totalCount === 0) return null;
106
- if (!previewMode) {
107
- if (dismissed) return null;
108
- if (allComplete) return null;
109
- }
110
+ const handleRetry = async () => {
111
+ setRetrying(true);
112
+ try {
113
+ await refresh();
114
+ } finally {
115
+ setRetrying(false);
116
+ }
117
+ };
118
+ const errorNotice =
119
+ error &&
120
+ (previewMode ||
121
+ !summaryKnown ||
122
+ (totalCount > 0 && !dismissed && !allComplete)) ? (
123
+ <div role="alert" style={styles.errorNotice}>
124
+ <span>{t("agentChat.common.chunkLoadFailed")}</span>
125
+ <Button
126
+ type="button"
127
+ variant="outline"
128
+ size="xs"
129
+ data-testid="onboarding-summary-retry"
130
+ disabled={retrying}
131
+ onClick={() => void handleRetry()}
132
+ >
133
+ {retrying ? (
134
+ <IconLoader2 className="animate-spin" aria-hidden />
135
+ ) : null}
136
+ {t("agentChat.common.retry")}
137
+ </Button>
138
+ </div>
139
+ ) : null;
110
140
 
141
+ if (loading) return null;
142
+ if (!previewMode && !summaryKnown) {
143
+ if (!errorNotice) return null;
144
+ return (
145
+ <div className={className} style={styles.errorOnly}>
146
+ {errorNotice}
147
+ </div>
148
+ );
149
+ }
150
+ if (!previewMode && (dismissed || (totalCount > 0 && allComplete))) {
151
+ return null;
152
+ }
153
+ if (error && totalCount === 0) {
154
+ if (!errorNotice) return null;
155
+ return (
156
+ <div className={className} style={styles.errorOnly}>
157
+ {errorNotice}
158
+ </div>
159
+ );
160
+ }
161
+ if (totalCount === 0) return null;
111
162
  if (!expanded) {
112
163
  return (
113
164
  <div className={className} style={styles.compactBanner}>
@@ -139,6 +190,7 @@ export function OnboardingPanel({
139
190
  </TooltipTrigger>
140
191
  <TooltipContent>Expand setup</TooltipContent>
141
192
  </Tooltip>
193
+ {errorNotice}
142
194
  </div>
143
195
  );
144
196
  }
@@ -174,6 +226,8 @@ export function OnboardingPanel({
174
226
  </Tooltip>
175
227
  </div>
176
228
 
229
+ {errorNotice}
230
+
177
231
  <div style={styles.list}>
178
232
  {steps.map((step) => (
179
233
  <StepCard
@@ -853,6 +907,19 @@ function badgeStyle(
853
907
  }
854
908
 
855
909
  const styles: Record<string, React.CSSProperties> = {
910
+ errorOnly: {
911
+ borderBottom: "1px solid hsl(var(--border) / 0.7)",
912
+ background: "hsl(var(--muted) / 0.25)",
913
+ fontSize: 12,
914
+ },
915
+ errorNotice: {
916
+ display: "flex",
917
+ alignItems: "center",
918
+ justifyContent: "space-between",
919
+ gap: 8,
920
+ padding: "8px 12px",
921
+ color: "hsl(var(--muted-foreground))",
922
+ },
856
923
  root: {
857
924
  borderBottom: "1px solid hsl(var(--border) / 0.7)",
858
925
  background: "hsl(var(--muted) / 0.25)",