@agent-native/core 0.141.6 → 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 (210) 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 +11 -6
  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/migrate-first-party-analytics-to-bigquery.ts +135 -0
  8. package/corpus/templates/analytics/actions/navigate.ts +1 -1
  9. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -2
  10. package/corpus/templates/analytics/actions/update-dashboard.ts +0 -1
  11. package/corpus/templates/analytics/actions/view-screen.ts +0 -20
  12. package/corpus/templates/analytics/app/components/layout/Header.tsx +0 -1
  13. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +5 -5
  14. package/corpus/templates/analytics/app/i18n-data.ts +2 -90
  15. package/corpus/templates/analytics/app/pages/Agents.tsx +0 -3
  16. package/corpus/templates/analytics/app/pages/Settings.tsx +22 -43
  17. package/corpus/templates/analytics/app/pages/settings/settings-search.ts +23 -20
  18. package/corpus/templates/analytics/app/routes/extensions._index.tsx +1 -1
  19. package/corpus/templates/analytics/app/routes/settings.$.tsx +1 -0
  20. package/corpus/templates/analytics/app/routes/team.tsx +1 -1
  21. package/corpus/templates/analytics/changelog/2026-08-05-settings-no-longer-includes-an-unnecessary-about-section.md +6 -0
  22. package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +1 -1
  23. package/corpus/templates/analytics/scripts/first-party-analytics-bigquery.sql +104 -0
  24. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +0 -2
  25. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +1058 -0
  26. package/corpus/templates/analytics/server/lib/first-party-analytics-health.ts +34 -15
  27. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +57 -4
  28. package/corpus/templates/analytics/server/lib/real-data-actions.ts +1 -3
  29. package/corpus/templates/analytics/server/plugins/agent-chat.ts +2 -4
  30. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +8 -6
  31. package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +16 -1
  32. package/corpus/templates/assets/AGENTS.md +1 -0
  33. package/corpus/templates/assets/actions/_helpers.ts +12 -0
  34. package/corpus/templates/assets/actions/generate-image.ts +9 -3
  35. package/corpus/templates/assets/actions/import-style-from-url.ts +115 -0
  36. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +3 -1
  37. package/corpus/templates/assets/app/routes/extensions._index.tsx +1 -1
  38. package/corpus/templates/assets/app/routes/settings.$.tsx +1 -0
  39. package/corpus/templates/assets/app/routes/settings.tsx +5 -0
  40. package/corpus/templates/assets/app/routes/team.tsx +1 -1
  41. package/corpus/templates/assets/changelog/2026-08-05-brand-kits-can-import-a-website-s-hydrated-visual-language-a.md +6 -0
  42. package/corpus/templates/assets/changelog/2026-08-05-generated-images-now-hand-off-embeddable-previews-to-other-w.md +6 -0
  43. package/corpus/templates/assets/server/lib/generation.ts +58 -0
  44. package/corpus/templates/assets/shared/api.ts +12 -0
  45. package/corpus/templates/brain/app/routes/extensions._index.tsx +1 -1
  46. package/corpus/templates/brain/app/routes/team.tsx +1 -1
  47. package/corpus/templates/brain/app/routes.ts +1 -1
  48. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +3 -1
  49. package/corpus/templates/calendar/app/routes/_app.extensions._index.tsx +1 -1
  50. package/corpus/templates/calendar/app/routes/_app.settings.$.tsx +1 -0
  51. package/corpus/templates/calendar/app/routes/_app.team.tsx +1 -1
  52. package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
  53. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +1 -1
  54. package/corpus/templates/chat/app/root.tsx +1 -1
  55. package/corpus/templates/chat/app/routes/agent.tsx +7 -7
  56. package/corpus/templates/chat/app/routes/extensions._index.tsx +1 -1
  57. package/corpus/templates/chat/app/routes/settings.$.tsx +1 -0
  58. package/corpus/templates/chat/app/routes/team.tsx +1 -1
  59. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  60. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +1 -1
  61. package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +1 -1
  62. package/corpus/templates/clips/app/routes/_app.extensions._index.tsx +1 -1
  63. package/corpus/templates/clips/app/routes/_app.settings.$.tsx +1 -0
  64. package/corpus/templates/content/app/routes/_app.extensions._index.tsx +1 -1
  65. package/corpus/templates/content/app/routes/_app.settings.$.tsx +1 -0
  66. package/corpus/templates/content/app/routes/_app.team.tsx +1 -1
  67. package/corpus/templates/content/e2e/global-setup.ts +1 -1
  68. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +6 -1
  69. package/corpus/templates/design/actions/analyze-brand-assets.ts +7 -7
  70. package/corpus/templates/design/actions/import-from-url.ts +6 -5
  71. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +2 -2
  72. package/corpus/templates/design/app/routes/extensions._index.tsx +1 -1
  73. package/corpus/templates/design/app/routes/settings.$.tsx +1 -0
  74. package/corpus/templates/design/app/routes/team.tsx +1 -1
  75. package/corpus/templates/design/changelog/2026-08-05-website-url-imports-now-capture-hydrated-browser-computed-de.md +6 -0
  76. package/corpus/templates/design/e2e/global-setup.ts +1 -1
  77. package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +1 -1
  78. package/corpus/templates/dispatch/app/routes/settings.$.tsx +1 -0
  79. package/corpus/templates/dispatch/app/routes/team.tsx +1 -1
  80. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +1 -1
  81. package/corpus/templates/factory/app/routes/extensions._index.tsx +1 -1
  82. package/corpus/templates/factory/app/routes/settings.$.tsx +1 -0
  83. package/corpus/templates/factory/app/routes/team.tsx +1 -1
  84. package/corpus/templates/forms/app/routes/_app.extensions._index.tsx +1 -1
  85. package/corpus/templates/forms/app/routes/_app.settings.$.tsx +1 -0
  86. package/corpus/templates/forms/app/routes/_app.team.tsx +1 -1
  87. package/corpus/templates/forms/shared/navigation.ts +1 -1
  88. package/corpus/templates/macros/app/routes/extensions._index.tsx +1 -1
  89. package/corpus/templates/macros/app/routes/settings.$.tsx +1 -0
  90. package/corpus/templates/macros/app/routes/team.tsx +1 -1
  91. package/corpus/templates/mail/app/routes/extensions._index.tsx +1 -1
  92. package/corpus/templates/mail/app/routes/settings.$.tsx +1 -0
  93. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +1 -1
  94. package/corpus/templates/plan/app/routes/extensions._index.tsx +1 -1
  95. package/corpus/templates/plan/app/routes/settings.$.tsx +1 -0
  96. package/corpus/templates/plan/app/routes/team.tsx +1 -1
  97. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +3 -1
  98. package/corpus/templates/slides/actions/analyze-brand-assets.ts +7 -7
  99. package/corpus/templates/slides/actions/import-from-url.ts +6 -6
  100. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +1 -1
  101. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +3 -0
  102. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +9 -3
  103. package/corpus/templates/slides/app/context/DeckContext.tsx +71 -20
  104. package/corpus/templates/slides/app/global.css +6 -0
  105. package/corpus/templates/slides/app/lib/create-deck-generation.ts +9 -5
  106. package/corpus/templates/slides/app/pages/DeckEditor.tsx +17 -0
  107. package/corpus/templates/slides/app/pages/Index.tsx +16 -5
  108. package/corpus/templates/slides/app/routes/extensions._index.tsx +1 -1
  109. package/corpus/templates/slides/app/routes/settings.$.tsx +1 -0
  110. package/corpus/templates/slides/app/routes/team.tsx +1 -1
  111. package/corpus/templates/slides/changelog/2026-08-05-failed-deck-creation-and-reference-uploads-now-recover-clean.md +6 -0
  112. package/corpus/templates/slides/changelog/2026-08-05-slides-recover-completed-generation-results-after-a-sync-hic.md +6 -0
  113. package/corpus/templates/slides/changelog/2026-08-05-website-url-imports-now-capture-hydrated-browser-computed-st.md +6 -0
  114. package/corpus/templates/tasks/app/components/layout/Header.tsx +3 -1
  115. package/corpus/templates/tasks/app/routes/extensions._index.tsx +1 -1
  116. package/corpus/templates/tasks/app/routes/settings.$.tsx +1 -0
  117. package/dist/agent/engine/ai-sdk-engine.js +49 -3
  118. package/dist/agent/production-agent.d.ts +1 -0
  119. package/dist/agent/production-agent.js +8 -0
  120. package/dist/agent/types.d.ts +7 -0
  121. package/dist/client/agent-chat-adapter.d.ts +1 -1
  122. package/dist/client/agent-chat-adapter.js +30 -3
  123. package/dist/client/agent-page/AgentWorkspaceContent.d.ts +1 -1
  124. package/dist/client/agent-page/AgentWorkspaceContent.js +13 -13
  125. package/dist/client/chat/run-recovery.js +5 -1
  126. package/dist/client/embed-auth.js +6 -2
  127. package/dist/client/error-format.js +12 -0
  128. package/dist/client/integrations/IntegrationsPanel.js +40 -6
  129. package/dist/client/org/OrgSwitcher.js +2 -2
  130. package/dist/client/org/TeamPage.js +92 -72
  131. package/dist/client/require-session.d.ts +1 -1
  132. package/dist/client/require-session.js +3 -3
  133. package/dist/client/resources/use-mcp-servers.d.ts +5 -0
  134. package/dist/client/resources/use-mcp-servers.js +36 -0
  135. package/dist/client/settings/AccountSettingsCard.js +40 -35
  136. package/dist/client/settings/AutomationsSection.js +1 -1
  137. package/dist/client/settings/DemoModeSection.d.ts +3 -1
  138. package/dist/client/settings/DemoModeSection.js +4 -1
  139. package/dist/client/settings/ExtensionsSettingsContent.js +1 -1
  140. package/dist/client/settings/SchedulingTimezoneField.d.ts +3 -1
  141. package/dist/client/settings/SchedulingTimezoneField.js +9 -5
  142. package/dist/client/settings/SettingsPanel.d.ts +4 -1
  143. package/dist/client/settings/SettingsPanel.js +21 -21
  144. package/dist/client/settings/SettingsTabsPage.d.ts +2 -2
  145. package/dist/client/settings/SettingsTabsPage.js +98 -45
  146. package/dist/client/settings/VoiceTranscriptionSection.js +6 -1
  147. package/dist/client/settings/useSettingsPanelController.js +47 -8
  148. package/dist/collab/struct-routes.d.ts +1 -1
  149. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  150. package/dist/ingestion/website.d.ts +30 -0
  151. package/dist/localization/default-messages.d.ts +5 -0
  152. package/dist/localization/default-messages.js +5 -0
  153. package/dist/mcp-client/oauth-routes.js +1 -1
  154. package/dist/mcp-client/routes.d.ts +1 -0
  155. package/dist/mcp-client/routes.js +49 -0
  156. package/dist/navigation/index.js +16 -5
  157. package/dist/observability/routes.d.ts +5 -5
  158. package/dist/progress/routes.d.ts +1 -1
  159. package/dist/provider-api/actions/custom-provider-registration.d.ts +13 -13
  160. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  161. package/dist/resources/handlers.d.ts +1 -1
  162. package/dist/scripts/call-agent.js +11 -0
  163. package/dist/secrets/routes.d.ts +3 -3
  164. package/dist/server/auth.js +9 -7
  165. package/dist/server/identity-sso.js +2 -1
  166. package/dist/server/onboarding-html.js +57 -34
  167. package/dist/server/realtime-token.d.ts +1 -1
  168. package/dist/server/transcribe-voice.d.ts +1 -1
  169. package/dist/server/workspace-provider-oauth.js +1 -1
  170. package/dist/setup-connections/onboarding.js +1 -1
  171. package/dist/shared/index.d.ts +1 -1
  172. package/dist/shared/index.js +1 -1
  173. package/dist/shared/sign-in-journey.d.ts +5 -2
  174. package/dist/shared/sign-in-journey.js +10 -4
  175. package/dist/templates/chat/app/components/layout/Layout.tsx +1 -1
  176. package/dist/templates/chat/app/hooks/use-navigation-state.ts +1 -1
  177. package/dist/templates/chat/app/root.tsx +1 -1
  178. package/dist/templates/chat/app/routes/agent.tsx +7 -7
  179. package/dist/templates/chat/app/routes/extensions._index.tsx +1 -1
  180. package/dist/templates/chat/app/routes/settings.$.tsx +1 -0
  181. package/dist/templates/chat/app/routes/team.tsx +1 -1
  182. package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +37 -6
  183. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +4 -2
  184. package/docs/content/authentication.mdx +4 -5
  185. package/docs/content/template-analytics-developers.mdx +1 -1
  186. package/docs/content/template-calendar-developers.mdx +1 -1
  187. package/docs/content/template-chat-developers.mdx +2 -2
  188. package/docs/content/template-content-developers.mdx +1 -1
  189. package/docs/content/template-mail-developers.mdx +1 -1
  190. package/docs/content/template-plan-developers.mdx +1 -1
  191. package/docs/content/toolkit-command-navigation.mdx +6 -6
  192. package/docs/content/toolkit-org-team.mdx +1 -1
  193. package/docs/content/toolkit-settings.mdx +8 -8
  194. package/docs/content/toolkit-setup-connections.mdx +1 -1
  195. package/package.json +1 -1
  196. package/src/templates/chat/app/components/layout/Layout.tsx +1 -1
  197. package/src/templates/chat/app/hooks/use-navigation-state.ts +1 -1
  198. package/src/templates/chat/app/root.tsx +1 -1
  199. package/src/templates/chat/app/routes/agent.tsx +7 -7
  200. package/src/templates/chat/app/routes/extensions._index.tsx +1 -1
  201. package/src/templates/chat/app/routes/settings.$.tsx +1 -0
  202. package/src/templates/chat/app/routes/team.tsx +1 -1
  203. package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +37 -6
  204. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +4 -2
  205. package/corpus/templates/analytics/.agents/skills/dashboard-management/references/template-catalog-and-demo.md +0 -62
  206. package/corpus/templates/analytics/actions/install-dashboard-template.ts +0 -369
  207. package/corpus/templates/analytics/actions/list-dashboard-templates.ts +0 -47
  208. package/corpus/templates/analytics/actions/list-native-v2-dashboard-templates.ts +0 -34
  209. package/corpus/templates/analytics/app/routes/catalog.tsx +0 -240
  210. package/src/templates/default/actions/navigate.spec.ts +0 -53
@@ -138,6 +138,25 @@ export interface Deck {
138
138
  aspectRatio?: AspectRatio;
139
139
  }
140
140
 
141
+ export type DeckPersistenceResult =
142
+ | { persisted: true }
143
+ | { persisted: false; reason: "request-failed"; error: unknown }
144
+ | { persisted: false; reason: "not-found" };
145
+
146
+ export function describeDeckPersistenceFailure(
147
+ result: DeckPersistenceResult,
148
+ fallback: string,
149
+ ): string {
150
+ if (result.persisted || result.reason === "not-found") return fallback;
151
+ if (result.error instanceof Error && result.error.message.trim()) {
152
+ return result.error.message;
153
+ }
154
+ if (typeof result.error === "string" && result.error.trim()) {
155
+ return result.error;
156
+ }
157
+ return fallback;
158
+ }
159
+
141
160
  interface DeckContextType {
142
161
  decks: Deck[];
143
162
  loading: boolean;
@@ -146,7 +165,7 @@ interface DeckContextType {
146
165
  title?: string,
147
166
  options?: { noDefaultSlides?: boolean; designSystemId?: string | null },
148
167
  ) => Deck;
149
- ensureDeckPersisted: (id: string) => Promise<boolean>;
168
+ ensureDeckPersisted: (id: string) => Promise<DeckPersistenceResult>;
150
169
  /**
151
170
  * Optimistically duplicate a deck. Inserts a copy into local state with the
152
171
  * supplied `newId` immediately so the UI can navigate without awaiting the
@@ -167,6 +186,7 @@ interface DeckContextType {
167
186
  ) => void;
168
187
  reloadDecks: () => Promise<void>;
169
188
  reloadDecksWithStatus: () => Promise<DeckReloadStatus>;
189
+ refreshOpenDeck: (deckId: string) => Promise<Deck | null>;
170
190
  getDeck: (id: string) => Deck | undefined;
171
191
  addSlide: (
172
192
  deckId: string,
@@ -885,7 +905,22 @@ async function fetchDecksForCurrentRoute(): Promise<Deck[] | null> {
885
905
  }
886
906
 
887
907
  async function deleteDeckFromAPI(id: string): Promise<void> {
888
- await callAction("delete-deck", { id }, { method: "DELETE" });
908
+ try {
909
+ await callAction("delete-deck", { id }, { method: "DELETE" });
910
+ } catch (error) {
911
+ // Deleting an optimistic deck is intentionally idempotent. A create can
912
+ // fail after the server committed the row, or before it created one.
913
+ if (
914
+ !(
915
+ error &&
916
+ typeof error === "object" &&
917
+ "status" in error &&
918
+ (error as { status?: unknown }).status === 404
919
+ )
920
+ ) {
921
+ throw error;
922
+ }
923
+ }
889
924
  }
890
925
 
891
926
  async function createDeckOnAPI(deck: Deck): Promise<void> {
@@ -1081,7 +1116,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1081
1116
  const deletion = pendingCreate
1082
1117
  ? pendingCreate.then(
1083
1118
  () => deleteDeckFromAPI(deckId),
1084
- () => undefined,
1119
+ () => deleteDeckFromAPI(deckId),
1085
1120
  )
1086
1121
  : deleteDeckFromAPI(deckId);
1087
1122
  void deletion.catch((err) => {
@@ -1267,11 +1302,11 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1267
1302
  // - Dirty deck / unsaved local create → additive merge only: surface
1268
1303
  // agent-added slides without ever overwriting or dropping local slides.
1269
1304
  const refetchOpenDeckIfChanged = useCallback(
1270
- async (currentOpenId: string) => {
1305
+ async (currentOpenId: string): Promise<Deck | null> => {
1271
1306
  const serverDeck = await fetchDeckFromAPI(currentOpenId);
1272
1307
  // Null means 404 (row not created yet), a transient failure, or a
1273
1308
  // still-pending create — nothing authoritative to reconcile.
1274
- if (!serverDeck) return;
1309
+ if (!serverDeck) return null;
1275
1310
  const clientDeck = decksRef.current.find((d) => d.id === currentOpenId);
1276
1311
 
1277
1312
  const hasLocalEdits =
@@ -1281,7 +1316,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1281
1316
  if (hasLocalEdits && clientDeck) {
1282
1317
  // Content-preserving: only ADD server slides missing locally.
1283
1318
  const merged = mergeServerAddedSlides(clientDeck, serverDeck);
1284
- if (merged === clientDeck) return; // nothing new to surface
1319
+ if (merged === clientDeck) return serverDeck; // nothing new to surface
1285
1320
  lastExternalUpdateRef.current = Date.now();
1286
1321
  setDecks((prev) => {
1287
1322
  const idx = prev.findIndex((d) => d.id === currentOpenId);
@@ -1290,16 +1325,17 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1290
1325
  next[idx] = merged;
1291
1326
  return next;
1292
1327
  });
1293
- return;
1328
+ return serverDeck;
1294
1329
  }
1295
1330
 
1296
1331
  const changed =
1297
1332
  !clientDeck ||
1298
1333
  clientDeck.updatedAt !== serverDeck.updatedAt ||
1299
- clientDeck.slides.length !== serverDeck.slides.length;
1300
- if (!changed) return;
1334
+ deckContentSignature(clientDeck) !== deckContentSignature(serverDeck);
1335
+ if (!changed) return serverDeck;
1301
1336
  lastExternalUpdateRef.current = Date.now();
1302
1337
  applyRemoteDeckUpdate(serverDeck);
1338
+ return serverDeck;
1303
1339
  },
1304
1340
  [applyRemoteDeckUpdate],
1305
1341
  );
@@ -1728,19 +1764,33 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1728
1764
  [noteLocalCreate, setDecksLocal],
1729
1765
  );
1730
1766
 
1731
- const ensureDeckPersisted = useCallback(async (id: string) => {
1732
- const pendingCreate = pendingCreatePromisesRef.current.get(id);
1733
- if (pendingCreate) {
1734
- try {
1735
- await pendingCreate;
1736
- return true;
1737
- } catch {
1738
- return false;
1767
+ const ensureDeckPersisted = useCallback(
1768
+ async (id: string): Promise<DeckPersistenceResult> => {
1769
+ const pendingCreate = pendingCreatePromisesRef.current.get(id);
1770
+ if (pendingCreate) {
1771
+ try {
1772
+ await pendingCreate;
1773
+ return { persisted: true };
1774
+ } catch (error) {
1775
+ return { persisted: false, reason: "request-failed", error };
1776
+ }
1739
1777
  }
1740
- }
1741
1778
 
1742
- return (await fetchDeckFromAPI(id)) !== null;
1743
- }, []);
1779
+ try {
1780
+ const result = await callAction<unknown>(
1781
+ "get-deck",
1782
+ { id },
1783
+ { method: "GET" },
1784
+ );
1785
+ return normalizeActionDeck(result)
1786
+ ? { persisted: true }
1787
+ : { persisted: false, reason: "not-found" };
1788
+ } catch (error) {
1789
+ return { persisted: false, reason: "request-failed", error };
1790
+ }
1791
+ },
1792
+ [],
1793
+ );
1744
1794
 
1745
1795
  const duplicateDeck = useCallback(
1746
1796
  (sourceDeckId: string, newId: string, title?: string): Deck | null => {
@@ -2150,6 +2200,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
2150
2200
  updateDeck,
2151
2201
  reloadDecks,
2152
2202
  reloadDecksWithStatus,
2203
+ refreshOpenDeck: refetchOpenDeckIfChanged,
2153
2204
  getDeck,
2154
2205
  addSlide,
2155
2206
  updateSlide,
@@ -14,6 +14,12 @@
14
14
 
15
15
  @source "./**/*.{ts,tsx}";
16
16
 
17
+ /* Keep the editable agent prompt readable even if the generated text utility
18
+ is missing or loses a cascade tie during the hosted build. */
19
+ .agent-composer-root [data-agent-composer-slot="editor-input"] {
20
+ color: hsl(var(--foreground));
21
+ }
22
+
17
23
  /* Hide the sidebar's built-in collapsed tab strip — AgentToggleButton in the
18
24
  toolbar serves this purpose instead, so we don't want a redundant bar. */
19
25
  button[title="Open agent sidebar"] {
@@ -4,7 +4,7 @@ import { flushSync } from "react-dom";
4
4
 
5
5
  import type { NewDeckReferenceSelection } from "@/components/editor/NewDeckReferenceStep";
6
6
  import type { UploadedFile } from "@/components/editor/PromptDialog";
7
- import type { Deck } from "@/context/DeckContext";
7
+ import type { Deck, DeckPersistenceResult } from "@/context/DeckContext";
8
8
  import { createDeckAgentMessage } from "@/lib/agent-visible-message";
9
9
 
10
10
  interface DesignSystemGenerationContextResult {
@@ -150,13 +150,17 @@ export interface StartDeckGenerationOptions {
150
150
  selectedReferenceDeckId?: string | null;
151
151
  designSystems: Array<{ id: string; title: string }>;
152
152
  createDeck: CreateDeck;
153
- ensureDeckPersisted: (id: string) => Promise<boolean>;
153
+ ensureDeckPersisted: (id: string) => Promise<DeckPersistenceResult>;
154
154
  deleteDeck: (id: string) => void;
155
155
  navigate: Navigate;
156
156
  agentSubmit: SubmitAgent;
157
157
  onPromptClosed: () => void;
158
158
  onUnauthenticated: (prompt: string, hadFiles: boolean) => void;
159
- onPersistenceFailure: (prompt: string, files: UploadedFile[]) => void;
159
+ onPersistenceFailure: (
160
+ prompt: string,
161
+ files: UploadedFile[],
162
+ failure: DeckPersistenceResult,
163
+ ) => void;
160
164
  }
161
165
 
162
166
  /** Create the optimistic deck, hydrate references, and start the agent run. */
@@ -214,8 +218,8 @@ export async function startDeckGeneration({
214
218
  onPromptClosed();
215
219
 
216
220
  const persisted = await ensureDeckPersisted(deck.id);
217
- if (!persisted) {
218
- onPersistenceFailure(prompt, filesForGeneration);
221
+ if (!persisted.persisted) {
222
+ onPersistenceFailure(prompt, filesForGeneration, persisted);
219
223
  deleteDeck(deckId);
220
224
  return "failed";
221
225
  }
@@ -155,6 +155,7 @@ export default function DeckEditor() {
155
155
  getDeck,
156
156
  reloadDecks,
157
157
  reloadDecksWithStatus,
158
+ refreshOpenDeck,
158
159
  updateDeck,
159
160
  updateSlide,
160
161
  deleteSlide,
@@ -396,6 +397,22 @@ export default function DeckEditor() {
396
397
  }
397
398
  }, [refetchOrg, reloadDecks]);
398
399
 
400
+ // The final generation write can race the last sync event. Pull the
401
+ // authoritative open deck when the run settles so a stale canvas does not
402
+ // require a browser refresh to reveal completed slides.
403
+ useEffect(() => {
404
+ if (
405
+ !id ||
406
+ !shouldClearNewDeckGeneratingState({
407
+ generating,
408
+ generationStarted: newDeckGenerationStarted.current,
409
+ })
410
+ ) {
411
+ return;
412
+ }
413
+ void refreshOpenDeck(id);
414
+ }, [generating, id, refreshOpenDeck]);
415
+
399
416
  // Clean up the generating URL param/ref when generation completes or when
400
417
  // the first slide lands, so partial progress is visible during long decks.
401
418
  useEffect(() => {
@@ -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: 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
@@ -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}`;
@@ -227,7 +227,11 @@ export function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDism
227
227
  const canRecover = info.recoverable === true;
228
228
  const shouldShowBuilderReconnect = isBuilderReconnectRunError(info);
229
229
  const isProviderAuthError = isProviderAuthenticationError([info.message, info.details].filter(Boolean).join("\n"), info.errorCode);
230
- const canRetry = canRecover || isProviderAuthError;
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;
231
235
  const builderReconnectResolved = shouldShowBuilderReconnect &&
232
236
  builderReconnect.hasFetchedStatus &&
233
237
  builderReconnect.configured;