@agent-native/core 0.141.5 → 0.141.7

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 (235) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +3 -14
  3. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +20 -8
  4. package/corpus/templates/analytics/.agents/skills/prometheus/SKILL.md +5 -5
  5. package/corpus/templates/analytics/AGENTS.md +2 -2
  6. package/corpus/templates/analytics/actions/compose-dashboard.ts +1 -1
  7. package/corpus/templates/analytics/actions/get-first-party-analytics-health.ts +15 -1
  8. package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +135 -0
  9. package/corpus/templates/analytics/actions/navigate.ts +1 -1
  10. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -2
  11. package/corpus/templates/analytics/actions/update-dashboard.ts +0 -1
  12. package/corpus/templates/analytics/actions/view-screen.ts +0 -20
  13. package/corpus/templates/analytics/app/components/layout/Header.tsx +0 -1
  14. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +5 -5
  15. package/corpus/templates/analytics/app/i18n-data.ts +330 -90
  16. package/corpus/templates/analytics/app/pages/Agents.tsx +0 -3
  17. package/corpus/templates/analytics/app/pages/DataSources.tsx +66 -30
  18. package/corpus/templates/analytics/app/pages/Settings.tsx +22 -43
  19. package/corpus/templates/analytics/app/pages/settings/settings-search.ts +23 -20
  20. package/corpus/templates/analytics/app/routes/extensions._index.tsx +1 -1
  21. package/corpus/templates/analytics/app/routes/settings.$.tsx +1 -0
  22. package/corpus/templates/analytics/app/routes/team.tsx +1 -1
  23. package/corpus/templates/analytics/changelog/2026-08-05-historical-analytics-rollups-now-repair-automatically-after-.md +6 -0
  24. package/corpus/templates/analytics/changelog/2026-08-05-settings-no-longer-includes-an-unnecessary-about-section.md +6 -0
  25. package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +1 -1
  26. package/corpus/templates/analytics/netlify.toml +4 -0
  27. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +109 -0
  28. package/corpus/templates/analytics/scripts/first-party-analytics-bigquery.sql +104 -0
  29. package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +342 -0
  30. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +0 -2
  31. package/corpus/templates/analytics/server/lib/analytics-rollup-lock.ts +8 -0
  32. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +11 -29
  33. package/corpus/templates/analytics/server/lib/dashboards-store.ts +55 -0
  34. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +1058 -0
  35. package/corpus/templates/analytics/server/lib/first-party-analytics-health.ts +151 -46
  36. package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +15 -9
  37. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +59 -17
  38. package/corpus/templates/analytics/server/lib/real-data-actions.ts +1 -3
  39. package/corpus/templates/analytics/server/plugins/agent-chat.ts +2 -4
  40. package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +61 -0
  41. package/corpus/templates/analytics/server/plugins/db.ts +58 -187
  42. package/corpus/templates/analytics/server/routes/api/analytics-rollups/backfill.post.ts +53 -0
  43. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +8 -6
  44. package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +16 -1
  45. package/corpus/templates/assets/AGENTS.md +1 -0
  46. package/corpus/templates/assets/actions/_helpers.ts +12 -0
  47. package/corpus/templates/assets/actions/generate-image.ts +9 -3
  48. package/corpus/templates/assets/actions/import-style-from-url.ts +115 -0
  49. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +3 -1
  50. package/corpus/templates/assets/app/routes/extensions._index.tsx +1 -1
  51. package/corpus/templates/assets/app/routes/settings.$.tsx +1 -0
  52. package/corpus/templates/assets/app/routes/settings.tsx +5 -0
  53. package/corpus/templates/assets/app/routes/team.tsx +1 -1
  54. package/corpus/templates/assets/changelog/2026-08-05-brand-kits-can-import-a-website-s-hydrated-visual-language-a.md +6 -0
  55. package/corpus/templates/assets/changelog/2026-08-05-generated-images-now-hand-off-embeddable-previews-to-other-w.md +6 -0
  56. package/corpus/templates/assets/server/lib/generation.ts +58 -0
  57. package/corpus/templates/assets/shared/api.ts +12 -0
  58. package/corpus/templates/brain/app/routes/extensions._index.tsx +1 -1
  59. package/corpus/templates/brain/app/routes/team.tsx +1 -1
  60. package/corpus/templates/brain/app/routes.ts +1 -1
  61. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +3 -1
  62. package/corpus/templates/calendar/app/routes/_app.extensions._index.tsx +1 -1
  63. package/corpus/templates/calendar/app/routes/_app.settings.$.tsx +1 -0
  64. package/corpus/templates/calendar/app/routes/_app.team.tsx +1 -1
  65. package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
  66. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +1 -1
  67. package/corpus/templates/chat/app/root.tsx +1 -1
  68. package/corpus/templates/chat/app/routes/agent.tsx +7 -7
  69. package/corpus/templates/chat/app/routes/extensions._index.tsx +1 -1
  70. package/corpus/templates/chat/app/routes/settings.$.tsx +1 -0
  71. package/corpus/templates/chat/app/routes/team.tsx +1 -1
  72. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  73. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +1 -1
  74. package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +1 -1
  75. package/corpus/templates/clips/app/routes/_app.extensions._index.tsx +1 -1
  76. package/corpus/templates/clips/app/routes/_app.settings.$.tsx +1 -0
  77. package/corpus/templates/clips/changelog/2026-08-05-clips-overlays-now-use-clean-surfaces-without-clipped-shadow.md +6 -0
  78. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +2 -2
  79. package/corpus/templates/clips/desktop/src/styles.css +3 -54
  80. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +3 -3
  81. package/corpus/templates/content/app/routes/_app.extensions._index.tsx +1 -1
  82. package/corpus/templates/content/app/routes/_app.settings.$.tsx +1 -0
  83. package/corpus/templates/content/app/routes/_app.team.tsx +1 -1
  84. package/corpus/templates/content/e2e/global-setup.ts +1 -1
  85. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +6 -1
  86. package/corpus/templates/design/actions/analyze-brand-assets.ts +7 -7
  87. package/corpus/templates/design/actions/get-design-system.ts +3 -1
  88. package/corpus/templates/design/actions/import-from-url.ts +6 -5
  89. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +2 -2
  90. package/corpus/templates/design/app/routes/extensions._index.tsx +1 -1
  91. package/corpus/templates/design/app/routes/settings.$.tsx +1 -0
  92. package/corpus/templates/design/app/routes/team.tsx +1 -1
  93. package/corpus/templates/design/changelog/2026-08-05-missing-design-systems-now-return-a-clear-not-found-response.md +6 -0
  94. package/corpus/templates/design/changelog/2026-08-05-website-url-imports-now-capture-hydrated-browser-computed-de.md +6 -0
  95. package/corpus/templates/design/e2e/global-setup.ts +1 -1
  96. package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +1 -1
  97. package/corpus/templates/dispatch/app/routes/settings.$.tsx +1 -0
  98. package/corpus/templates/dispatch/app/routes/team.tsx +1 -1
  99. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +1 -1
  100. package/corpus/templates/factory/app/routes/extensions._index.tsx +1 -1
  101. package/corpus/templates/factory/app/routes/settings.$.tsx +1 -0
  102. package/corpus/templates/factory/app/routes/team.tsx +1 -1
  103. package/corpus/templates/forms/app/routes/_app.extensions._index.tsx +1 -1
  104. package/corpus/templates/forms/app/routes/_app.settings.$.tsx +1 -0
  105. package/corpus/templates/forms/app/routes/_app.team.tsx +1 -1
  106. package/corpus/templates/forms/shared/navigation.ts +1 -1
  107. package/corpus/templates/macros/app/routes/extensions._index.tsx +1 -1
  108. package/corpus/templates/macros/app/routes/settings.$.tsx +1 -0
  109. package/corpus/templates/macros/app/routes/team.tsx +1 -1
  110. package/corpus/templates/mail/app/routes/extensions._index.tsx +1 -1
  111. package/corpus/templates/mail/app/routes/settings.$.tsx +1 -0
  112. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +1 -1
  113. package/corpus/templates/plan/app/routes/extensions._index.tsx +1 -1
  114. package/corpus/templates/plan/app/routes/settings.$.tsx +1 -0
  115. package/corpus/templates/plan/app/routes/team.tsx +1 -1
  116. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +3 -1
  117. package/corpus/templates/slides/actions/analyze-brand-assets.ts +7 -7
  118. package/corpus/templates/slides/actions/import-from-url.ts +6 -6
  119. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +2 -2
  120. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +1 -1
  121. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +3 -0
  122. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -1
  123. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +9 -3
  124. package/corpus/templates/slides/app/context/DeckContext.tsx +71 -20
  125. package/corpus/templates/slides/app/global.css +6 -0
  126. package/corpus/templates/slides/app/i18n/en-US.ts +5 -5
  127. package/corpus/templates/slides/app/lib/create-deck-generation.ts +9 -5
  128. package/corpus/templates/slides/app/pages/DeckEditor.tsx +17 -0
  129. package/corpus/templates/slides/app/pages/Index.tsx +16 -5
  130. package/corpus/templates/slides/app/routes/extensions._index.tsx +1 -1
  131. package/corpus/templates/slides/app/routes/settings.$.tsx +1 -0
  132. package/corpus/templates/slides/app/routes/team.tsx +1 -1
  133. package/corpus/templates/slides/changelog/2026-08-05-failed-deck-creation-and-reference-uploads-now-recover-clean.md +6 -0
  134. package/corpus/templates/slides/changelog/2026-08-05-new-presentation-prompts-now-clearly-describe-presentation-g.md +6 -0
  135. package/corpus/templates/slides/changelog/2026-08-05-slides-recover-completed-generation-results-after-a-sync-hic.md +6 -0
  136. package/corpus/templates/slides/changelog/2026-08-05-website-url-imports-now-capture-hydrated-browser-computed-st.md +6 -0
  137. package/corpus/templates/tasks/app/components/layout/Header.tsx +3 -1
  138. package/corpus/templates/tasks/app/routes/extensions._index.tsx +1 -1
  139. package/corpus/templates/tasks/app/routes/settings.$.tsx +1 -0
  140. package/dist/agent/engine/ai-sdk-engine.js +49 -3
  141. package/dist/agent/production-agent.d.ts +1 -0
  142. package/dist/agent/production-agent.js +8 -0
  143. package/dist/agent/types.d.ts +7 -0
  144. package/dist/client/AssistantChat.js +1 -1
  145. package/dist/client/agent-chat-adapter.d.ts +1 -1
  146. package/dist/client/agent-chat-adapter.js +30 -3
  147. package/dist/client/agent-page/AgentWorkspaceContent.d.ts +1 -1
  148. package/dist/client/agent-page/AgentWorkspaceContent.js +13 -13
  149. package/dist/client/chat/run-recovery.d.ts +2 -1
  150. package/dist/client/chat/run-recovery.js +14 -3
  151. package/dist/client/embed-auth.js +6 -2
  152. package/dist/client/error-format.d.ts +1 -0
  153. package/dist/client/error-format.js +16 -2
  154. package/dist/client/integrations/IntegrationsPanel.js +40 -6
  155. package/dist/client/org/OrgSwitcher.js +2 -2
  156. package/dist/client/org/TeamPage.js +92 -72
  157. package/dist/client/require-session.d.ts +1 -1
  158. package/dist/client/require-session.js +3 -3
  159. package/dist/client/resources/use-mcp-servers.d.ts +5 -0
  160. package/dist/client/resources/use-mcp-servers.js +36 -0
  161. package/dist/client/settings/AccountSettingsCard.js +40 -35
  162. package/dist/client/settings/AutomationsSection.js +1 -1
  163. package/dist/client/settings/DemoModeSection.d.ts +3 -1
  164. package/dist/client/settings/DemoModeSection.js +4 -1
  165. package/dist/client/settings/ExtensionsSettingsContent.js +1 -1
  166. package/dist/client/settings/SchedulingTimezoneField.d.ts +3 -1
  167. package/dist/client/settings/SchedulingTimezoneField.js +9 -5
  168. package/dist/client/settings/SettingsPanel.d.ts +4 -1
  169. package/dist/client/settings/SettingsPanel.js +21 -21
  170. package/dist/client/settings/SettingsTabsPage.d.ts +2 -2
  171. package/dist/client/settings/SettingsTabsPage.js +98 -45
  172. package/dist/client/settings/VoiceTranscriptionSection.js +6 -1
  173. package/dist/client/settings/useSettingsPanelController.js +47 -8
  174. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  175. package/dist/ingestion/website.d.ts +30 -0
  176. package/dist/localization/default-messages.d.ts +5 -0
  177. package/dist/localization/default-messages.js +5 -0
  178. package/dist/mcp-client/oauth-routes.js +1 -1
  179. package/dist/mcp-client/routes.d.ts +1 -0
  180. package/dist/mcp-client/routes.js +49 -0
  181. package/dist/navigation/index.js +16 -5
  182. package/dist/observability/routes.d.ts +3 -3
  183. package/dist/provider-api/actions/custom-provider-registration.d.ts +13 -13
  184. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  185. package/dist/scripts/call-agent.js +11 -0
  186. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  187. package/dist/server/auth.js +9 -7
  188. package/dist/server/identity-sso.js +2 -1
  189. package/dist/server/onboarding-html.js +57 -34
  190. package/dist/server/realtime-token.d.ts +1 -1
  191. package/dist/server/transcribe-voice.d.ts +1 -1
  192. package/dist/server/workspace-provider-oauth.js +1 -1
  193. package/dist/setup-connections/onboarding.js +1 -1
  194. package/dist/shared/index.d.ts +1 -1
  195. package/dist/shared/index.js +1 -1
  196. package/dist/shared/sign-in-journey.d.ts +5 -2
  197. package/dist/shared/sign-in-journey.js +10 -4
  198. package/dist/templates/chat/app/components/layout/Layout.tsx +1 -1
  199. package/dist/templates/chat/app/hooks/use-navigation-state.ts +1 -1
  200. package/dist/templates/chat/app/root.tsx +1 -1
  201. package/dist/templates/chat/app/routes/agent.tsx +7 -7
  202. package/dist/templates/chat/app/routes/extensions._index.tsx +1 -1
  203. package/dist/templates/chat/app/routes/settings.$.tsx +1 -0
  204. package/dist/templates/chat/app/routes/team.tsx +1 -1
  205. package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +37 -6
  206. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +4 -2
  207. package/docs/content/authentication.mdx +4 -5
  208. package/docs/content/template-analytics-developers.mdx +1 -1
  209. package/docs/content/template-calendar-developers.mdx +1 -1
  210. package/docs/content/template-chat-developers.mdx +2 -2
  211. package/docs/content/template-content-developers.mdx +1 -1
  212. package/docs/content/template-mail-developers.mdx +1 -1
  213. package/docs/content/template-plan-developers.mdx +1 -1
  214. package/docs/content/toolkit-command-navigation.mdx +6 -6
  215. package/docs/content/toolkit-org-team.mdx +1 -1
  216. package/docs/content/toolkit-settings.mdx +8 -8
  217. package/docs/content/toolkit-setup-connections.mdx +1 -1
  218. package/package.json +1 -1
  219. package/src/templates/chat/app/components/layout/Layout.tsx +1 -1
  220. package/src/templates/chat/app/hooks/use-navigation-state.ts +1 -1
  221. package/src/templates/chat/app/root.tsx +1 -1
  222. package/src/templates/chat/app/routes/agent.tsx +7 -7
  223. package/src/templates/chat/app/routes/extensions._index.tsx +1 -1
  224. package/src/templates/chat/app/routes/settings.$.tsx +1 -0
  225. package/src/templates/chat/app/routes/team.tsx +1 -1
  226. package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +37 -6
  227. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +4 -2
  228. package/corpus/templates/analytics/.agents/skills/dashboard-management/references/template-catalog-and-demo.md +0 -62
  229. package/corpus/templates/analytics/actions/install-dashboard-template.ts +0 -369
  230. package/corpus/templates/analytics/actions/list-dashboard-templates.ts +0 -47
  231. package/corpus/templates/analytics/actions/list-native-v2-dashboard-templates.ts +0 -34
  232. package/corpus/templates/analytics/app/routes/catalog.tsx +0 -240
  233. package/corpus/templates/analytics/changelog/2026-08-05-historical-analytics-rollups-stay-complete-and-atomic.md +0 -6
  234. package/corpus/templates/analytics/changelog/2026-08-05-historical-rollups-stay-complete-while-live-events-arrive.md +0 -6
  235. package/src/templates/default/actions/navigate.spec.ts +0 -53
@@ -40,7 +40,10 @@ import {
40
40
  } from "@/components/ui/alert-dialog";
41
41
  import { Button } from "@/components/ui/button";
42
42
  import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
43
- import type { Deck } from "@/context/DeckContext";
43
+ import {
44
+ describeDeckPersistenceFailure,
45
+ type Deck,
46
+ } from "@/context/DeckContext";
44
47
  import { useDecks } from "@/context/DeckContext";
45
48
  import { useAgentGenerating } from "@/hooks/use-agent-generating";
46
49
  import { useDesignSystems } from "@/hooks/use-design-systems";
@@ -571,14 +574,17 @@ export default function Index() {
571
574
  setNewDeckPromptOpen(false);
572
575
 
573
576
  const persisted = await ensureDeckPersisted(deck.id);
574
- if (!persisted) {
577
+ if (!persisted.persisted) {
575
578
  if (!savePromptForRetry(prompt)) {
576
579
  setNewDeckInitialPrompt({ text: prompt, key: Date.now() });
577
580
  }
578
581
  setNewDeckRetryFiles(filesForGeneration);
579
582
  deleteDeck(deckId);
580
583
  toast.error(t("home.generationStartFailed"), {
581
- description: t("home.generationStartFailedDescription"),
584
+ description: describeDeckPersistenceFailure(
585
+ persisted,
586
+ t("home.generationStartFailedDescription"),
587
+ ),
582
588
  });
583
589
  setShowNewDeckPrompt(true);
584
590
  return;
@@ -747,9 +753,14 @@ export default function Index() {
747
753
  noDefaultSlides: true,
748
754
  });
749
755
  const persisted = await ensureDeckPersisted(referenceDeck.id);
750
- if (!persisted) {
756
+ if (!persisted.persisted) {
751
757
  deleteDeck(referenceDeck.id);
752
- throw new Error("The PDF reference deck could not be saved.");
758
+ throw new Error(
759
+ describeDeckPersistenceFailure(
760
+ persisted,
761
+ "The PDF reference deck could not be saved.",
762
+ ),
763
+ );
753
764
  }
754
765
  try {
755
766
  const imported = (await callAction("import-file", {
@@ -1,5 +1,5 @@
1
1
  import { Navigate } from "react-router";
2
2
 
3
3
  export default function ExtensionsRoute() {
4
- return <Navigate to="/settings#extensions" replace />;
4
+ return <Navigate to="/settings/extensions" replace />;
5
5
  }
@@ -0,0 +1 @@
1
+ export { default, meta } from "./settings.js";
@@ -7,5 +7,5 @@ export function meta() {
7
7
  }
8
8
 
9
9
  export default function TeamRoute() {
10
- return <Navigate to="/settings#organization" replace />;
10
+ return <Navigate to="/settings/organization" replace />;
11
11
  }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-05
4
+ ---
5
+
6
+ Failed deck creation and reference uploads now recover cleanly instead of leaving stuck drafts
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-05
4
+ ---
5
+
6
+ New presentation prompts now clearly describe presentation generation
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-05
4
+ ---
5
+
6
+ Slides recover completed generation results after a sync hiccup, and the agent prompt stays readable.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-05
4
+ ---
5
+
6
+ Website URL imports now capture hydrated browser-computed styles for more faithful deck themes.
@@ -11,7 +11,9 @@ type Translate = ReturnType<typeof useT>;
11
11
 
12
12
  function resolveTitle(t: Translate, pathname: string): string {
13
13
  if (pathname === "/tasks") return t("header.pageTasks");
14
- if (pathname === "/settings") return t("header.pageSettings");
14
+ if (pathname === "/settings" || pathname.startsWith("/settings/")) {
15
+ return t("header.pageSettings");
16
+ }
15
17
  if (pathname === "/team") return t("header.pageTeam");
16
18
  if (pathname.startsWith("/extensions")) return t("header.pageExtensions");
17
19
  return APP_TITLE;
@@ -8,5 +8,5 @@ export function meta() {
8
8
  }
9
9
 
10
10
  export default function ExtensionsRoute() {
11
- return <Navigate to="/settings#extensions" replace />;
11
+ return <Navigate to="/settings/extensions" replace />;
12
12
  }
@@ -0,0 +1 @@
1
+ export { default, meta } from "./settings.js";
@@ -193,9 +193,14 @@ class AISDKEngine {
193
193
  // `builder-engine` and `anthropic-engine` already fail closed here; this
194
194
  // engine was the only one that did not.
195
195
  //
196
- // A configured `baseUrl` is exempt: a self-hosted or local gateway may
197
- // legitimately accept unauthenticated requests.
198
- if (!this.apiKey && !this.baseUrl && this.requiredEnvVars.length > 0) {
196
+ // A LOCAL `baseUrl` is exempt: a self-hosted gateway on the same machine or
197
+ // private network may legitimately accept unauthenticated requests. A public
198
+ // one may not — every hosted provider requires a key, so exempting any
199
+ // baseUrl at all reopened the same doomed unauthenticated request this
200
+ // guard exists to stop, just for anyone pointing at a remote gateway.
201
+ if (!this.apiKey &&
202
+ !isLocalBaseUrl(this.baseUrl) &&
203
+ this.requiredEnvVars.length > 0) {
199
204
  yield {
200
205
  type: "stop",
201
206
  reason: "error",
@@ -531,6 +536,47 @@ async function importProviderPackage(provider) {
531
536
  return import("ai-sdk-ollama");
532
537
  }
533
538
  }
539
+ /**
540
+ * True only for a gateway on this machine or a private network — the case the
541
+ * keyless exemption was written for. Anything routable on the public internet
542
+ * (openrouter.ai, an api.* host, a cloud proxy) needs credentials, so treating
543
+ * it as keyless just sends a request that cannot succeed.
544
+ *
545
+ * An unparseable value is NOT local: this gates a security-shaped decision, so
546
+ * the ambiguous case has to take the safe branch and require a key.
547
+ */
548
+ function isLocalBaseUrl(baseUrl) {
549
+ if (!baseUrl)
550
+ return false;
551
+ let host;
552
+ try {
553
+ host = new URL(baseUrl).hostname.toLowerCase();
554
+ }
555
+ catch (error) {
556
+ // An invalid URL is never a local exemption; fail closed.
557
+ if (error instanceof TypeError)
558
+ return false;
559
+ throw error;
560
+ }
561
+ if (host === "localhost" || host.endsWith(".localhost"))
562
+ return true;
563
+ if (host === "::1" || host === "[::1]")
564
+ return true;
565
+ if (host.endsWith(".local") || host.endsWith(".internal"))
566
+ return true;
567
+ // IPv4 loopback and the RFC1918 private ranges.
568
+ const v4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
569
+ if (!v4)
570
+ return false;
571
+ const [a, b] = [Number(v4[1]), Number(v4[2])];
572
+ if (a === 127 || a === 10)
573
+ return true;
574
+ if (a === 192 && b === 168)
575
+ return true;
576
+ if (a === 172 && b >= 16 && b <= 31)
577
+ return true;
578
+ return false;
579
+ }
534
580
  function capitalize(s) {
535
581
  return s.charAt(0).toUpperCase() + s.slice(1);
536
582
  }
@@ -922,6 +922,7 @@ export interface ChainServerDrivenContinuationDeps {
922
922
  updateRunStatusIfRunning?: typeof updateRunStatusIfRunning;
923
923
  markRunAborted?: typeof markRunAborted;
924
924
  setRunTerminalReason?: typeof setRunTerminalReason;
925
+ setRunError?: typeof setRunError;
925
926
  recordRunDiagnostic?: typeof recordRunDiagnostic;
926
927
  markBackgroundContinuationChunkTerminal?: typeof markBackgroundContinuationChunkTerminal;
927
928
  generateRunId?: typeof generateRunId;
@@ -5271,6 +5271,7 @@ export async function chainServerDrivenContinuation(opts) {
5271
5271
  updateRunStatusIfRunning: opts.deps?.updateRunStatusIfRunning ?? updateRunStatusIfRunning,
5272
5272
  markRunAborted: opts.deps?.markRunAborted ?? markRunAborted,
5273
5273
  setRunTerminalReason: opts.deps?.setRunTerminalReason ?? setRunTerminalReason,
5274
+ setRunError: opts.deps?.setRunError ?? setRunError,
5274
5275
  recordRunDiagnostic: opts.deps?.recordRunDiagnostic ?? recordRunDiagnostic,
5275
5276
  markBackgroundContinuationChunkTerminal: opts.deps?.markBackgroundContinuationChunkTerminal ??
5276
5277
  markBackgroundContinuationChunkTerminal,
@@ -5545,6 +5546,13 @@ export async function chainServerDrivenContinuation(opts) {
5545
5546
  await d
5546
5547
  .setRunTerminalReason(runId, "background_continuation_dispatch_failed")
5547
5548
  .catch(() => { });
5549
+ // Record the cause on the row itself, not only inside diag_stage's JSON.
5550
+ // Without this the run goes terminal with error_code and error_detail
5551
+ // both NULL, so every dashboard and query reads it as a failure with no
5552
+ // known cause and the real message is only findable by parsing a blob.
5553
+ await d
5554
+ .setRunError(runId, "background_continuation_dispatch_failed", chainErr instanceof Error ? chainErr.message : String(chainErr))
5555
+ .catch(() => { });
5548
5556
  }
5549
5557
  }
5550
5558
  }
@@ -275,6 +275,13 @@ export type AgentChatEvent = {
275
275
  /** Remote task to resume when status is pending/input-required. */
276
276
  taskId?: string;
277
277
  durationMs?: number;
278
+ /**
279
+ * Why the call ended, on a terminal status. Already computed for
280
+ * telemetry; without it here the persisted event says only that a
281
+ * cross-app call failed after N ms and never why, so a failed A2A call
282
+ * cannot be diagnosed from the database without a repro.
283
+ */
284
+ terminalCode?: string;
278
285
  } | {
279
286
  /**
280
287
  * Periodic liveness for an in-flight cross-app A2A call. Emitted by the
@@ -4007,7 +4007,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
4007
4007
  } }) })), shouldShowRunError && visibleRunError && (_jsx(MessageScrollerItem, { children: _jsx(RunErrorRecoveryCard, { info: visibleRunError, onContinue: () => {
4008
4008
  setRunErrorInfo(null);
4009
4009
  addToQueue(RECONNECT_NO_PROGRESS_CONTINUE_MESSAGE, undefined, undefined, undefined, undefined, "queued", "continue");
4010
- }, onRetry: retryAfterRunError, onFork: onForkChat, onDismiss: () => {
4010
+ }, onRetry: retryAfterRunError, onFork: onForkChat, onProviderConnected: handleBuilderConnected, onDismiss: () => {
4011
4011
  if (visibleRunErrorKey) {
4012
4012
  setDismissedRunErrorKey(visibleRunErrorKey);
4013
4013
  }
@@ -14,7 +14,7 @@ export type AgentChatSurfaceKind =
14
14
  /** Chat hosted by Desktop for a local app with explicit code access. */
15
15
  | "desktop";
16
16
  export declare const BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS = 210000;
17
- export declare const MAX_FOLLOWED_BACKGROUND_RUNS = 6;
17
+ export declare const MAX_FOLLOWED_BACKGROUND_RUNS = 24;
18
18
  export declare const MAX_BACKGROUND_FOLLOW_WALL_TIME_MS: number;
19
19
  /**
20
20
  * Server truth for "does this run still look alive?", used to keep the client
@@ -131,8 +131,30 @@ export const BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS = 210_000;
131
131
  // every poll tick and the follow loop never exits (prod: 26 chained runs over
132
132
  // 22 minutes, all error:stale_run, user watching a spinner). These bound the
133
133
  // whole turn instead, and an identical repeated failure counts as no progress.
134
- export const MAX_FOLLOWED_BACKGROUND_RUNS = 6;
135
- export const MAX_BACKGROUND_FOLLOW_WALL_TIME_MS = 10 * 60_000;
134
+ //
135
+ // CLIENT-ABOVE-SERVER INVARIANT (asserted in agent-chat-adapter.spec.ts).
136
+ // These are a backstop for a server that has gone silent in a way the idle
137
+ // timeout misses — NOT the primary limit. They must stay ABOVE the server's
138
+ // own ceilings so the server, which can actually tell progress from looping,
139
+ // always terminates a turn first and writes a truthful terminal reason:
140
+ // BACKGROUND_SOFT_TIMEOUT_CEILING_MS (13 min, run-manager.ts) — one chunk
141
+ // MAX_TURN_WALL_CLOCK_MS (90 min, production-agent.ts) — one turn
142
+ // MAX_BACKGROUND_RUN_CONTINUATIONS (20, production-agent.ts)
143
+ //
144
+ // They were originally set to 10 min / 6 runs, which put the whole-turn client
145
+ // budget BELOW the 13-minute ceiling of a single legal chunk. Any turn needing
146
+ // a second full-length chunk was killed by the client while the server was
147
+ // healthy and had 80 minutes left — measured in prod as turns dying at 11-25
148
+ // minutes with `last_progress_at` tracking the abort, i.e. still streaming
149
+ // tokens and completing tools when the client gave up. That is the top
150
+ // non-auth cause of "the chat just stopped" reports.
151
+ //
152
+ // Killing a turn that is NOT progressing is already covered twice over, by
153
+ // mechanisms that read progress rather than a clock: the 210s idle timeout
154
+ // above, and MAX_REPEATED_BACKGROUND_TERMINAL_REASONS below. Do not re-tighten
155
+ // these two to catch a stuck turn — fix the progress signal instead.
156
+ export const MAX_FOLLOWED_BACKGROUND_RUNS = 24;
157
+ export const MAX_BACKGROUND_FOLLOW_WALL_TIME_MS = 95 * 60_000;
136
158
  const MAX_REPEATED_BACKGROUND_TERMINAL_REASONS = 3;
137
159
  // A re-observed terminal run whose outcome would be an ERROR (never a
138
160
  // genuine "done" success) gets a short extra grace window before the follow
@@ -2185,7 +2207,12 @@ export function createAgentChatAdapter(options) {
2185
2207
  if (Date.now() - followStartedAt >=
2186
2208
  MAX_BACKGROUND_FOLLOW_WALL_TIME_MS) {
2187
2209
  yield* stopBackgroundTurnBeforeReporting({
2188
- message: `The agent kept restarting this step for ${Math.round(MAX_BACKGROUND_FOLLOW_WALL_TIME_MS / 60_000)} minutes without finishing it. It was stopped so you can retry from the preserved chat context — a smaller request usually gets through.`,
2210
+ // Says only what this backstop actually knows. It fires on a
2211
+ // clock, so it cannot tell a looping turn from one that was
2212
+ // still working — the old copy asserted "kept restarting
2213
+ // without finishing", which prod runs contradicted: they were
2214
+ // streaming tokens right up to the abort.
2215
+ message: `This turn ran for ${Math.round(MAX_BACKGROUND_FOLLOW_WALL_TIME_MS / 60_000)} minutes without finishing, so it was stopped. Your chat context is preserved — retrying, or splitting this into smaller requests, usually gets through.`,
2189
2216
  errorCode: "background_follow_time_budget_exhausted",
2190
2217
  details: `followed_runs: ${followedRunIds.size}`,
2191
2218
  });
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
- export type AgentWorkspaceTab = "overview" | "resources" | "automations" | "agents" | "access";
2
+ export type AgentWorkspaceTab = "overview" | "resources" | "automations" | "agents";
3
3
  export declare function AgentWorkspaceContent({ overview, className, activeTab, }: {
4
4
  overview: ReactNode;
5
5
  activeTab: AgentWorkspaceTab;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { IconBook2, IconBolt, IconChecklist, IconFolder, IconHierarchy2, IconHistory, IconNotes, IconShieldLock, IconTopologyRing2, } from "@tabler/icons-react";
2
+ import { IconBook2, IconBolt, IconChecklist, IconFolder, IconHierarchy2, IconHistory, IconNotes, IconTopologyRing2, } from "@tabler/icons-react";
3
3
  import { lazy, Suspense, useEffect, useState } from "react";
4
4
  import { AgentsSection } from "../settings/AgentsSection.js";
5
5
  import { cn } from "../utils.js";
@@ -21,7 +21,6 @@ const HUB_TABS = [
21
21
  { id: "resources", label: "Resources", icon: IconFolder },
22
22
  { id: "automations", label: "Automations", icon: IconBolt },
23
23
  { id: "agents", label: "Connected agents", icon: IconTopologyRing2 },
24
- { id: "access", label: "Access", icon: IconShieldLock },
25
24
  ];
26
25
  const RESOURCE_IDS = new Set(RESOURCE_TABS.map((tab) => tab.id));
27
26
  function normalizeHash(value) {
@@ -31,9 +30,15 @@ function initialHubState() {
31
30
  if (typeof window === "undefined") {
32
31
  return { tab: "overview", resource: "files" };
33
32
  }
33
+ const pathParts = window.location.pathname.split("/").filter(Boolean);
34
+ const settingsIndex = pathParts.indexOf("settings");
35
+ const settingsPath = settingsIndex === -1 ? [] : pathParts.slice(settingsIndex + 1);
36
+ const routeParts = settingsPath[0] === "agent" ? settingsPath : ["agent", ...settingsPath];
34
37
  const hash = normalizeHash(window.location.hash);
35
- const parts = hash.split(":");
36
- const scoped = parts[0] === "agent";
38
+ const hashParts = hash.split(":");
39
+ const pathScoped = settingsPath[0] === "agent";
40
+ const scoped = pathScoped || hashParts[0] === "agent";
41
+ const parts = pathScoped ? routeParts : hashParts;
37
42
  const requested = scoped ? parts[1] : hash;
38
43
  const resource = scoped && RESOURCE_IDS.has(parts[2])
39
44
  ? parts[2]
@@ -54,8 +59,6 @@ function initialHubState() {
54
59
  requested === "remote-agents") {
55
60
  return { tab: "agents", resource };
56
61
  }
57
- if (requested === "access")
58
- return { tab: "access", resource };
59
62
  return { tab: "overview", resource };
60
63
  }
61
64
  function ResourceContent({ resource }) {
@@ -65,8 +68,8 @@ function ResourceContent({ resource }) {
65
68
  const selected = tab.id === resource;
66
69
  return (_jsxs("button", { type: "button", onClick: () => {
67
70
  if (typeof window !== "undefined") {
68
- window.history.pushState(null, "", `${window.location.pathname}${window.location.search}#agent:resources:${tab.id}`);
69
- window.dispatchEvent(new Event("hashchange"));
71
+ window.history.pushState(null, "", `/settings/agent/resources/${tab.id}${window.location.search}`);
72
+ window.dispatchEvent(new Event("popstate"));
70
73
  }
71
74
  }, className: cn("inline-flex shrink-0 items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors", selected
72
75
  ? "bg-accent text-foreground"
@@ -75,9 +78,6 @@ function ResourceContent({ resource }) {
75
78
  ? "Add context the agent can use across conversations."
76
79
  : "Manage the resources that shape how the agent works." })] }), _jsx(Suspense, { fallback: _jsx("div", { className: "h-48 animate-pulse rounded-xl border border-border bg-muted/20" }), children: _jsx(ResourcesPanel, { showMcpServers: false, resourceFilter: resource, resourceTreeVariant: "collection", scope: "personal" }, resource) })] }));
77
80
  }
78
- function AccessContent() {
79
- return (_jsx("div", { children: _jsx("div", { className: "rounded-xl border border-border/70 bg-card text-card-foreground", children: _jsx("div", { className: "px-5 py-5 sm:px-6", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("span", { className: "flex size-9 shrink-0 items-center justify-center rounded-lg border border-border bg-background text-muted-foreground", children: _jsx(IconShieldLock, { className: "size-4" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "Agent access" }), _jsx("p", { className: "mt-1 text-sm leading-5 text-muted-foreground", children: "Use the agent card and MCP endpoints from connected clients." })] })] }) }) }) }));
80
- }
81
81
  export function AgentWorkspaceContent({ overview, className, activeTab, }) {
82
82
  const [{ resource }, setState] = useState(initialHubState);
83
83
  useEffect(() => {
@@ -98,8 +98,8 @@ export function AgentWorkspaceContent({ overview, className, activeTab, }) {
98
98
  ? "Schedule and manage agent tasks."
99
99
  : activeTab === "agents"
100
100
  ? "Connect agents to delegate work from chat."
101
- : "Control how other apps and agents access this agent.";
102
- return (_jsxs("div", { className: cn("w-full space-y-6", className), children: [_jsxs("header", { className: "space-y-1", children: [_jsx("h1", { className: "text-2xl font-semibold tracking-tight text-foreground", children: activeTab === "overview" ? "Agent" : page?.label }), _jsx("p", { className: "text-sm leading-5 text-muted-foreground", children: pageDescription })] }), activeTab === "overview" && overview, activeTab === "resources" && _jsx(ResourceContent, { resource: resource }), activeTab === "automations" && (_jsx(Suspense, { fallback: _jsx("div", { className: "h-48 animate-pulse rounded-xl border border-border bg-muted/20" }), children: _jsx(AgentJobsTab, { scope: "user", canManageOrg: true, hideHeader: true }) })), activeTab === "agents" && _jsx(AgentsSection, {}), activeTab === "access" && _jsx(AccessContent, {})] }));
101
+ : "Configure how your agent works, what it knows, and what it can do.";
102
+ return (_jsxs("div", { className: cn("w-full space-y-6", className), children: [_jsxs("header", { className: "space-y-1", children: [_jsx("h1", { className: "text-2xl font-semibold tracking-tight text-foreground", children: activeTab === "overview" ? "Agent" : page?.label }), _jsx("p", { className: "text-sm leading-5 text-muted-foreground", children: pageDescription })] }), activeTab === "overview" && overview, activeTab === "resources" && _jsx(ResourceContent, { resource: resource }), activeTab === "automations" && (_jsx(Suspense, { fallback: _jsx("div", { className: "h-48 animate-pulse rounded-xl border border-border bg-muted/20" }), children: _jsx(AgentJobsTab, { scope: "user", canManageOrg: true, hideHeader: true }) })), activeTab === "agents" && _jsx(AgentsSection, {})] }));
103
103
  }
104
104
  export function agentWorkspaceTabHash(tab) {
105
105
  return `agent:${tab}`;
@@ -35,12 +35,13 @@ export declare function BuilderSetupCard({ onConnected, bouncePulse, fullWidth,
35
35
  fullWidth?: boolean;
36
36
  layout?: BuilderSetupCardLayout;
37
37
  }): import("react").JSX.Element;
38
- export declare function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDismiss, }: {
38
+ export declare function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDismiss, onProviderConnected, }: {
39
39
  info: RunErrorInfo;
40
40
  onContinue: () => void;
41
41
  onRetry: () => void;
42
42
  onFork?: () => void | boolean | Promise<void | boolean>;
43
43
  onDismiss: () => void;
44
+ onProviderConnected?: () => void;
44
45
  }): import("react").JSX.Element;
45
46
  export declare function LoopLimitContinueCard({ info, onContinue, }: {
46
47
  info: LoopLimitInfo;
@@ -8,6 +8,7 @@ import { useState, useEffect, useCallback, useRef } from "react";
8
8
  import { saveAgentEngineApiKey, } from "../agent-engine-key.js";
9
9
  import { agentNativePath } from "../api-path.js";
10
10
  import { writeClipboardText } from "../clipboard.js";
11
+ import { isProviderAuthenticationError } from "../error-format.js";
11
12
  import { useT } from "../i18n.js";
12
13
  import { useBuilderConnectFlow } from "../settings/useBuilderStatus.js";
13
14
  import { cn } from "../utils.js";
@@ -215,7 +216,7 @@ export function BuilderSetupCard({ onConnected, bouncePulse, fullWidth, layout =
215
216
  : "mx-auto w-full max-w-[42rem] px-3 pb-2 sm:w-fit"), children: _jsx("div", { className: cn("agent-builder-setup-card__panel rounded-lg border border-border/80 bg-background/80 shadow-sm backdrop-blur", sidebarLayout ? "p-2.5" : "p-3"), children: _jsx(BuilderSetupContent, { onConnected: onConnected, layout: layout }) }) }));
216
217
  }
217
218
  // ─── RunErrorRecoveryCard ─────────────────────────────────────────────────────
218
- export function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDismiss, }) {
219
+ export function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDismiss, onProviderConnected, }) {
219
220
  const [detailsOpen, setDetailsOpen] = useState(false);
220
221
  const [copyState, setCopyState] = useState("idle");
221
222
  const [forking, setForking] = useState(false);
@@ -225,6 +226,12 @@ export function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDism
225
226
  });
226
227
  const canRecover = info.recoverable === true;
227
228
  const shouldShowBuilderReconnect = isBuilderReconnectRunError(info);
229
+ const isProviderAuthError = isProviderAuthenticationError([info.message, info.details].filter(Boolean).join("\n"), info.errorCode);
230
+ // Blocked on something the reader goes and fixes elsewhere, then comes back
231
+ // to. Without a retry the card is a dead end and its own copy ("then retry")
232
+ // points at a button that isn't there.
233
+ const isUnblockableExternally = info.errorCode === "email_verification_required";
234
+ const canRetry = canRecover || isProviderAuthError || isUnblockableExternally;
228
235
  const builderReconnectResolved = shouldShowBuilderReconnect &&
229
236
  builderReconnect.hasFetchedStatus &&
230
237
  builderReconnect.configured;
@@ -254,6 +261,10 @@ export function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDism
254
261
  window.dispatchEvent(new CustomEvent("agent-chat:new-chat"));
255
262
  onDismiss();
256
263
  }, [onDismiss]);
264
+ const handleProviderConnected = useCallback(() => {
265
+ onProviderConnected?.();
266
+ onDismiss();
267
+ }, [onDismiss, onProviderConnected]);
257
268
  const handleFork = useCallback(async () => {
258
269
  if (!onFork || forking)
259
270
  return;
@@ -277,9 +288,9 @@ export function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDism
277
288
  onDismiss();
278
289
  }
279
290
  }, [builderReconnectResolved, onDismiss]);
280
- return (_jsxs("div", { className: "rounded-lg border border-amber-500/25 bg-amber-500/[0.06] p-3 text-sm", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-amber-500/10 text-amber-700 dark:text-amber-300", children: _jsx(IconAlertTriangle, { size: 14 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "font-medium text-foreground", children: runErrorHeadline(info) }), _jsx("p", { className: "mt-1 text-xs leading-relaxed text-muted-foreground", children: info.message }), shouldShowBuilderReconnect && !builderReconnectResolved && (_jsx("p", { className: "mt-2 text-xs leading-relaxed text-muted-foreground", children: "The current Builder.io or model-provider credential was rejected. Reconnect Builder.io (free tier available), then retry this message." })), isConnectionRecoveryError && (_jsx("p", { className: "mt-2 text-xs leading-relaxed text-muted-foreground", children: "If retry lands on the same error, start a new chat session and continue from what already changed." })), (info.runId || info.errorCode || info.details) && (_jsxs("button", { type: "button", onClick: () => setDetailsOpen((v) => !v), className: "mt-2 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground hover:text-foreground", children: [_jsx(IconChevronDown, { size: 12, className: cn("transition-transform", detailsOpen && "rotate-180") }), "Details"] })), detailsOpen && (_jsxs("div", { className: "mt-2 rounded-md border border-border/60 bg-background/70 p-2 font-mono text-[11px] leading-relaxed text-muted-foreground", children: [info.runId && _jsxs("div", { children: ["run: ", info.runId] }), info.errorCode && _jsxs("div", { children: ["code: ", info.errorCode] }), info.details && (_jsx("pre", { className: "mt-2 max-h-28 overflow-auto whitespace-pre-wrap break-words font-mono", children: info.details }))] }))] }), _jsx("button", { type: "button", onClick: onDismiss, "aria-label": "Dismiss", className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-background/80 hover:text-foreground", children: _jsx(IconX, { size: 14 }) })] }), _jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2", children: [shouldShowBuilderReconnect && !builderReconnectResolved && (_jsxs("button", { type: "button", onClick: () => builderReconnect.start(), disabled: builderReconnect.connecting, className: "inline-flex h-8 items-center gap-1.5 rounded-md bg-foreground px-3 text-xs font-medium text-background hover:opacity-90 disabled:cursor-wait disabled:opacity-70", children: [builderReconnect.connecting ? (_jsx(IconLoader2, { size: 13, className: "animate-spin" })) : null, builderReconnect.connecting
291
+ return (_jsxs("div", { className: "rounded-lg border border-amber-500/25 bg-amber-500/[0.06] p-3 text-sm", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-amber-500/10 text-amber-700 dark:text-amber-300", children: _jsx(IconAlertTriangle, { size: 14 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "font-medium text-foreground", children: runErrorHeadline(info) }), _jsx("p", { className: "mt-1 text-xs leading-relaxed text-muted-foreground", children: info.message }), isProviderAuthError && (_jsx("div", { className: "mt-3 rounded-md border border-border/70 bg-background/60 p-2.5", children: _jsx(BuilderSetupContent, { layout: "sidebar", onConnected: handleProviderConnected }) })), shouldShowBuilderReconnect && !builderReconnectResolved && (_jsx("p", { className: "mt-2 text-xs leading-relaxed text-muted-foreground", children: "The current Builder.io or model-provider credential was rejected. Reconnect Builder.io (free tier available), then retry this message." })), isConnectionRecoveryError && (_jsx("p", { className: "mt-2 text-xs leading-relaxed text-muted-foreground", children: "If retry lands on the same error, start a new chat session and continue from what already changed." })), (info.runId || info.errorCode || info.details) && (_jsxs("button", { type: "button", onClick: () => setDetailsOpen((v) => !v), className: "mt-2 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground hover:text-foreground", children: [_jsx(IconChevronDown, { size: 12, className: cn("transition-transform", detailsOpen && "rotate-180") }), "Details"] })), detailsOpen && (_jsxs("div", { className: "mt-2 rounded-md border border-border/60 bg-background/70 p-2 font-mono text-[11px] leading-relaxed text-muted-foreground", children: [info.runId && _jsxs("div", { children: ["run: ", info.runId] }), info.errorCode && _jsxs("div", { children: ["code: ", info.errorCode] }), info.details && (_jsx("pre", { className: "mt-2 max-h-28 overflow-auto whitespace-pre-wrap break-words font-mono", children: info.details }))] }))] }), _jsx("button", { type: "button", onClick: onDismiss, "aria-label": "Dismiss", className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-background/80 hover:text-foreground", children: _jsx(IconX, { size: 14 }) })] }), _jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2", children: [shouldShowBuilderReconnect && !builderReconnectResolved && (_jsxs("button", { type: "button", onClick: () => builderReconnect.start(), disabled: builderReconnect.connecting, className: "inline-flex h-8 items-center gap-1.5 rounded-md bg-foreground px-3 text-xs font-medium text-background hover:opacity-90 disabled:cursor-wait disabled:opacity-70", children: [builderReconnect.connecting ? (_jsx(IconLoader2, { size: 13, className: "animate-spin" })) : null, builderReconnect.connecting
281
292
  ? "Connecting Builder.io"
282
- : "Reconnect Builder.io"] })), canRecover && (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: onContinue, className: "inline-flex h-8 items-center gap-1.5 rounded-md bg-foreground px-3 text-xs font-medium text-background hover:opacity-90", children: [_jsx(IconPlayerPlay, { size: 13 }), "Continue"] }), _jsxs("button", { type: "button", onClick: onRetry, className: "inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent", children: [_jsx(IconRefresh, { size: 13 }), isQueryError ? "Diagnose and retry" : "Retry"] })] })), canRecover && isConnectionRecoveryError && (_jsxs("button", { type: "button", onClick: startNewChat, className: "inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent", children: [_jsx(IconPlus, { size: 13 }), "New chat"] })), canRecover && onFork && !isConnectionRecoveryError && (_jsxs("button", { type: "button", onClick: handleFork, disabled: forking, title: "Fork this conversation into a separate chat thread.", "aria-label": "Fork this conversation into a separate chat thread", className: "inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent disabled:cursor-wait disabled:opacity-70", children: [forking ? (_jsx(IconLoader2, { size: 13, className: "animate-spin" })) : (_jsx(IconGitFork, { size: 13 })), forking ? "Forking..." : "Fork chat"] })), _jsxs("button", { type: "button", onClick: copyDetails, className: "ml-auto inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-xs font-medium text-muted-foreground hover:bg-background/80 hover:text-foreground", children: [copyState === "copied" ? (_jsx(IconCheck, { size: 13 })) : copyState === "failed" ? (_jsx(IconX, { size: 13 })) : (_jsx(IconCopy, { size: 13 })), _jsx("span", { "aria-live": "polite", children: copyState === "copied"
293
+ : "Reconnect Builder.io"] })), canRecover && (_jsx(_Fragment, { children: _jsxs("button", { type: "button", onClick: onContinue, className: "inline-flex h-8 items-center gap-1.5 rounded-md bg-foreground px-3 text-xs font-medium text-background hover:opacity-90", children: [_jsx(IconPlayerPlay, { size: 13 }), "Continue"] }) })), canRetry && (_jsxs("button", { type: "button", onClick: onRetry, className: "inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent", children: [_jsx(IconRefresh, { size: 13 }), isQueryError ? "Diagnose and retry" : "Retry"] })), canRecover && isConnectionRecoveryError && (_jsxs("button", { type: "button", onClick: startNewChat, className: "inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent", children: [_jsx(IconPlus, { size: 13 }), "New chat"] })), canRecover && onFork && !isConnectionRecoveryError && (_jsxs("button", { type: "button", onClick: handleFork, disabled: forking, title: "Fork this conversation into a separate chat thread.", "aria-label": "Fork this conversation into a separate chat thread", className: "inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent disabled:cursor-wait disabled:opacity-70", children: [forking ? (_jsx(IconLoader2, { size: 13, className: "animate-spin" })) : (_jsx(IconGitFork, { size: 13 })), forking ? "Forking..." : "Fork chat"] })), _jsxs("button", { type: "button", onClick: copyDetails, className: "ml-auto inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-xs font-medium text-muted-foreground hover:bg-background/80 hover:text-foreground", children: [copyState === "copied" ? (_jsx(IconCheck, { size: 13 })) : copyState === "failed" ? (_jsx(IconX, { size: 13 })) : (_jsx(IconCopy, { size: 13 })), _jsx("span", { "aria-live": "polite", children: copyState === "copied"
283
294
  ? "Copied"
284
295
  : copyState === "failed"
285
296
  ? "Copy failed"
@@ -1,4 +1,5 @@
1
1
  import { EMBED_MODE_QUERY_PARAM, EMBED_START_PATH, EMBED_TARGET_HEADER, EMBED_TARGET_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM, MCP_APP_CHAT_BRIDGE_QUERY_PARAM, } from "../shared/embed-auth.js";
2
+ import { SIGN_IN_ENTRY_PATH, SIGN_IN_LEGACY_ENTRY_PATH, } from "../shared/sign-in-journey.js";
2
3
  let installed = false;
3
4
  let memoryToken = null;
4
5
  let mcpChatBridgeActive = false;
@@ -322,9 +323,12 @@ function shouldGuardAuthFailure(method, url) {
322
323
  if (url.pathname === EMBED_START_PATH)
323
324
  return false;
324
325
  // Suffix, not equality: an app mounted under a base path serves
325
- // `/<app>/_agent-native/sign-in`, which the old exact match never matched.
326
- if (url.pathname.endsWith("/_agent-native/sign-in"))
326
+ // `/<app>/sign-in` (or the legacy framework path), which an exact match
327
+ // would miss.
328
+ if (url.pathname.endsWith(SIGN_IN_ENTRY_PATH) ||
329
+ url.pathname.endsWith(SIGN_IN_LEGACY_ENTRY_PATH)) {
327
330
  return false;
331
+ }
328
332
  return true;
329
333
  }
330
334
  function activeAuthFailure(record) {
@@ -18,4 +18,5 @@ export interface NormalizedChatError {
18
18
  message: string;
19
19
  details?: string;
20
20
  }
21
+ export declare function isProviderAuthenticationError(text: string, errorCode?: string): boolean;
21
22
  export declare function normalizeChatError(errorMessage: string, errorCode?: string): NormalizedChatError;
@@ -60,7 +60,7 @@ function isProviderRateLimit(text, errorCode) {
60
60
  /\b(?:http\s*)?429\b.*\b(?:status|too many requests|rate[-_\s]?limit|no body)\b/i.test(text) ||
61
61
  /\b(?:too many requests|rate[-_\s]?limit(?:ed| exceeded)?)\b/i.test(text));
62
62
  }
63
- function isProviderAuthenticationError(text, errorCode) {
63
+ export function isProviderAuthenticationError(text, errorCode) {
64
64
  const code = normalizeErrorCode(errorCode);
65
65
  const lower = text.toLowerCase();
66
66
  return (code === "authentication_error" ||
@@ -72,6 +72,8 @@ function isProviderAuthenticationError(text, errorCode) {
72
72
  lower.includes("invalid api key") ||
73
73
  lower.includes("incorrect api key") ||
74
74
  lower.includes("api key is invalid") ||
75
+ lower.includes("rejected the saved api key") ||
76
+ lower.includes("saved provider key was rejected") ||
75
77
  (lower.includes("authentication_error") && lower.includes("api")));
76
78
  }
77
79
  function isConnectionError(text, errorCode) {
@@ -98,6 +100,18 @@ export function normalizeChatError(errorMessage, errorCode) {
98
100
  details: text,
99
101
  };
100
102
  }
103
+ // Reaches us as a bare gateway 403 with no upgradeUrl, so before this case
104
+ // it fell all the way through to the raw upstream sentence under a generic
105
+ // "The agent hit an error" headline, with no retry and no action — a dead
106
+ // end. It was the single largest cause of turns ending without an answer in
107
+ // one app, and reads to the user as the chat being broken rather than as
108
+ // something one person can fix in a minute.
109
+ if (code === "email_verification_required") {
110
+ return {
111
+ message: "AI is paused until an email address in this workspace is verified. Check the inbox for the verification link, then retry.",
112
+ details: text,
113
+ };
114
+ }
101
115
  // A model/parameter combination this provider will never accept. Retrying is
102
116
  // pointless and the raw sentence names an API surface the reader has no way
103
117
  // to act on, so say what they can actually change.
@@ -115,7 +129,7 @@ export function normalizeChatError(errorMessage, errorCode) {
115
129
  }
116
130
  if (isProviderAuthenticationError(text, errorCode)) {
117
131
  return {
118
- message: "The model provider rejected the saved API key. Update the key in Settings → Integrations → API keys, then retry.",
132
+ message: "The saved provider key was rejected. Connect Builder.io for managed AI, or update your provider key, then retry.",
119
133
  details: text,
120
134
  };
121
135
  }