@agent-native/core 0.151.2 → 0.152.1

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 (259) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +1 -1
  3. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +24 -0
  4. package/corpus/templates/analytics/actions/bigquery-query.ts +21 -0
  5. package/corpus/templates/analytics/app/components/ConnectionTestStatus.tsx +65 -0
  6. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +3 -3
  7. package/corpus/templates/analytics/app/pages/DataSources.tsx +15 -37
  8. package/corpus/templates/analytics/app/root.tsx +0 -1
  9. package/corpus/templates/analytics/changelog/2026-08-11-analytics-custom-blocks-keep-loading-with-legacy-bigquery-action.md +6 -0
  10. package/corpus/templates/analytics/changelog/2026-08-11-analytics-routes-company-knowledge-and-slack-context-questio.md +6 -0
  11. package/corpus/templates/analytics/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  12. package/corpus/templates/analytics/public/manifest.json +0 -1
  13. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +5 -0
  14. package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
  15. package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +1 -1
  16. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +24 -0
  17. package/corpus/templates/assets/app/root.tsx +0 -1
  18. package/corpus/templates/assets/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  19. package/corpus/templates/assets/public/manifest.json +0 -1
  20. package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +1 -1
  21. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +24 -0
  22. package/corpus/templates/brain/app/root.tsx +0 -1
  23. package/corpus/templates/brain/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  24. package/corpus/templates/brain/public/manifest.json +0 -1
  25. package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +1 -1
  26. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +24 -0
  27. package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +0 -53
  28. package/corpus/templates/calendar/app/i18n-data.ts +0 -80
  29. package/corpus/templates/calendar/app/root.tsx +0 -1
  30. package/corpus/templates/calendar/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  31. package/corpus/templates/calendar/changelog/2026-08-11-google-calendar-connection-buttons-now-open-the-sign-in-flow.md +6 -0
  32. package/corpus/templates/calendar/public/manifest.json +0 -1
  33. package/corpus/templates/calendar/server/plugins/auth.ts +0 -10
  34. package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +1 -1
  35. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +24 -0
  36. package/corpus/templates/chat/app/root.tsx +0 -1
  37. package/corpus/templates/chat/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  38. package/corpus/templates/chat/public/manifest.json +0 -1
  39. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +24 -0
  40. package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +1 -1
  41. package/corpus/templates/clips/app/components/player/share-dialog.tsx +6 -0
  42. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +20 -3
  43. package/corpus/templates/clips/app/i18n/en-US.ts +4 -4
  44. package/corpus/templates/clips/app/root.tsx +0 -1
  45. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +1 -3
  46. package/corpus/templates/clips/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  47. package/corpus/templates/clips/changelog/2026-08-11-clips-desktop-settings-are-now-easier-to-scan-with-a-wider-t.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-08-11-google-calendar-connections-from-meetings-now-open-directly.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-08-11-recording-viewers-can-comment-and-react.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-08-11-system-audio-recording-now-lives-inside-the-microphone-menu-.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-08-11-timestamped-comments-stay-on-the-video-for-three-seconds-wh.md +6 -0
  52. package/corpus/templates/clips/desktop/src/app.tsx +773 -1001
  53. package/corpus/templates/clips/desktop/src/components/MediaDeviceRow.tsx +15 -27
  54. package/corpus/templates/clips/desktop/src/styles.css +364 -5
  55. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +1 -1
  56. package/corpus/templates/clips/public/manifest.json +0 -1
  57. package/corpus/templates/content/.agents/skills/sharing/SKILL.md +1 -1
  58. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +24 -0
  59. package/corpus/templates/content/AGENTS.md +3 -0
  60. package/corpus/templates/content/actions/resolve-content-landing.ts +120 -0
  61. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +196 -30
  62. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +9 -0
  63. package/corpus/templates/content/app/i18n-data.ts +58 -0
  64. package/corpus/templates/content/app/lib/content-landing.ts +22 -0
  65. package/corpus/templates/content/app/root.tsx +0 -1
  66. package/corpus/templates/content/app/routes/_app._index.tsx +36 -26
  67. package/corpus/templates/content/changelog/2026-08-03-choose-text-or-a-heading-from-one-compact-formatting-control.md +6 -0
  68. package/corpus/templates/content/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  69. package/corpus/templates/content/changelog/2026-08-11-content-opens-your-last-page-on-return-and-gives-new-users-a.md +6 -0
  70. package/corpus/templates/content/docs/product/capabilities/content.workspace.session-restore.md +7 -1
  71. package/corpus/templates/content/docs/solutions/evidence/content-text-selector/README.md +26 -0
  72. package/corpus/templates/content/parity/matrix.md +1 -0
  73. package/corpus/templates/content/parity/matrix.ts +22 -0
  74. package/corpus/templates/content/public/manifest.json +0 -1
  75. package/corpus/templates/content/shared/content-landing.ts +17 -0
  76. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +1 -1
  77. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +24 -0
  78. package/corpus/templates/crm/app/root.tsx +0 -1
  79. package/corpus/templates/crm/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  80. package/corpus/templates/crm/public/manifest.json +0 -1
  81. package/corpus/templates/design/.agents/skills/sharing/SKILL.md +1 -1
  82. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +24 -0
  83. package/corpus/templates/design/app/i18n/en-US.ts +5 -0
  84. package/corpus/templates/design/app/root.tsx +0 -1
  85. package/corpus/templates/design/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  86. package/corpus/templates/design/changelog/2026-08-11-design-sharing-now-identifies-viewers-who-can-add-review-com.md +6 -0
  87. package/corpus/templates/design/public/manifest.json +0 -1
  88. package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +1 -1
  89. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +24 -0
  90. package/corpus/templates/dispatch/AGENTS.md +9 -0
  91. package/corpus/templates/dispatch/actions/list-dispatch-usage-metrics.ts +5 -19
  92. package/corpus/templates/dispatch/actions/view-screen.ts +10 -3
  93. package/corpus/templates/dispatch/app/root.tsx +0 -1
  94. package/corpus/templates/dispatch/app/routes/settings.tsx +13 -1
  95. package/corpus/templates/dispatch/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  96. package/corpus/templates/dispatch/changelog/2026-08-11-clicking-the-dispatch-logo-returns-to-the-overview-page.md +6 -0
  97. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-no-longer-links-to-itself-from-the-app-switcher.md +6 -0
  98. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-settings-now-link-directly-to-admin.md +6 -0
  99. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-shows-usage-trends-recent-prompts-and-an-agent-revi.md +6 -0
  100. package/corpus/templates/dispatch/changelog/2026-08-11-human-friendly-app-names.md +6 -0
  101. package/corpus/templates/dispatch/public/manifest.json +0 -1
  102. package/corpus/templates/dispatch/server/lib/identity-sso.ts +432 -158
  103. package/corpus/templates/dispatch/server/plugins/identity-sso.ts +204 -123
  104. package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +1 -0
  105. package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +1 -1
  106. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +24 -0
  107. package/corpus/templates/factory/app/root.tsx +0 -1
  108. package/corpus/templates/factory/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  109. package/corpus/templates/factory/public/manifest.json +0 -1
  110. package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +1 -1
  111. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +24 -0
  112. package/corpus/templates/forms/app/root.tsx +0 -1
  113. package/corpus/templates/forms/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  114. package/corpus/templates/forms/public/manifest.json +0 -1
  115. package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +1 -1
  116. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +24 -0
  117. package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +1 -1
  118. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +24 -0
  119. package/corpus/templates/mail/app/root.tsx +0 -1
  120. package/corpus/templates/mail/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  121. package/corpus/templates/mail/changelog/2026-08-11-google-sign-in-now-opens-the-gmail-connection-flow-directly.md +6 -0
  122. package/corpus/templates/mail/public/manifest.json +0 -1
  123. package/corpus/templates/mail/server/plugins/auth.ts +0 -10
  124. package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +1 -1
  125. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +24 -0
  126. package/corpus/templates/plan/app/i18n/en-US.ts +2 -0
  127. package/corpus/templates/plan/app/pages/PlansPage.tsx +6 -0
  128. package/corpus/templates/plan/app/root.tsx +0 -1
  129. package/corpus/templates/plan/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  130. package/corpus/templates/plan/changelog/2026-08-11-plan-sharing-now-identifies-viewers-who-can-add-comments.md +6 -0
  131. package/corpus/templates/plan/public/manifest.json +0 -1
  132. package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +1 -1
  133. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +24 -0
  134. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -0
  135. package/corpus/templates/slides/app/i18n/en-US.ts +2 -0
  136. package/corpus/templates/slides/app/root.tsx +0 -1
  137. package/corpus/templates/slides/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  138. package/corpus/templates/slides/changelog/2026-08-11-share-dialog-uses-commenter-terminology.md +6 -0
  139. package/corpus/templates/slides/public/manifest.json +0 -1
  140. package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +1 -1
  141. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +24 -0
  142. package/corpus/templates/tasks/app/root.tsx +0 -1
  143. package/corpus/templates/tasks/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  144. package/corpus/templates/tasks/public/manifest.json +0 -1
  145. package/dist/agent/production-agent.d.ts +8 -0
  146. package/dist/agent/production-agent.js +67 -22
  147. package/dist/agent/run-manager.d.ts +7 -0
  148. package/dist/agent/run-manager.js +46 -3
  149. package/dist/agent/tool-approval-migrations.d.ts +40 -0
  150. package/dist/agent/tool-approval-migrations.js +51 -0
  151. package/dist/agent/tool-approval-store.js +5 -20
  152. package/dist/cli/templates-meta.js +2 -2
  153. package/dist/client/AgentPanel.js +10 -1
  154. package/dist/client/AssistantChat.d.ts +3 -0
  155. package/dist/client/AssistantChat.js +151 -141
  156. package/dist/client/agent-page/AgentTabsPage.js +2 -2
  157. package/dist/client/chat/attachment-adapters.js +8 -2
  158. package/dist/client/chat/tool-call-display.d.ts +1 -0
  159. package/dist/client/chat/tool-call-display.js +3 -1
  160. package/dist/client/chat-first.js +11 -5
  161. package/dist/client/guided-questions.d.ts +15 -1
  162. package/dist/client/guided-questions.js +33 -15
  163. package/dist/client/hooks/index.d.ts +1 -1
  164. package/dist/client/hooks/index.js +1 -1
  165. package/dist/client/org/OrgSwitcher.d.ts +3 -1
  166. package/dist/client/org/OrgSwitcher.js +22 -16
  167. package/dist/client/org/WorkspaceNotice.js +3 -1
  168. package/dist/client/resources/index.d.ts +1 -1
  169. package/dist/client/resources/index.js +1 -1
  170. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  171. package/dist/client/resources/use-mcp-servers.js +14 -0
  172. package/dist/client/settings/AccountSettingsCard.js +8 -4
  173. package/dist/client/settings/SettingsPanel.js +46 -13
  174. package/dist/client/settings/SettingsTabsPage.js +8 -3
  175. package/dist/client/sharing/ShareButton.d.ts +7 -1
  176. package/dist/client/sharing/ShareButton.js +14 -7
  177. package/dist/client/sharing/ShareDialog.js +5 -1
  178. package/dist/client/sharing/useShareButtonController.d.ts +4 -0
  179. package/dist/client/sharing/useShareButtonController.js +15 -0
  180. package/dist/client/sharing/useShareDialogController.d.ts +7 -0
  181. package/dist/client/sharing/useShareDialogController.js +14 -0
  182. package/dist/client/sse-event-processor.js +37 -0
  183. package/dist/client/use-session.d.ts +2 -0
  184. package/dist/client/use-session.js +69 -5
  185. package/dist/deploy/build.js +1 -1
  186. package/dist/file-upload/pre-upload-attachments.d.ts +3 -4
  187. package/dist/file-upload/pre-upload-attachments.js +65 -17
  188. package/dist/ingestion/index.d.ts +1 -0
  189. package/dist/ingestion/index.js +1 -0
  190. package/dist/ingestion/office.d.ts +3 -3
  191. package/dist/ingestion/office.js +24 -2
  192. package/dist/ingestion/spreadsheet.d.ts +41 -0
  193. package/dist/ingestion/spreadsheet.js +172 -0
  194. package/dist/integrations/adapters/slack.js +7 -3
  195. package/dist/integrations/webhook-handler.js +11 -3
  196. package/dist/localization/default-messages.d.ts +3 -0
  197. package/dist/localization/default-messages.js +3 -0
  198. package/dist/mcp/embed-app.js +20 -9
  199. package/dist/notifications/routes.d.ts +3 -3
  200. package/dist/oauth-tokens/migrations.d.ts +8 -0
  201. package/dist/oauth-tokens/migrations.js +36 -0
  202. package/dist/observability/routes.d.ts +5 -5
  203. package/dist/org/workspace-url.d.ts +8 -0
  204. package/dist/org/workspace-url.js +26 -2
  205. package/dist/progress/routes.d.ts +1 -1
  206. package/dist/secrets/routes.d.ts +6 -6
  207. package/dist/server/agent-chat/context-tools.js +27 -6
  208. package/dist/server/agent-chat-plugin.js +16 -8
  209. package/dist/server/auth.d.ts +0 -11
  210. package/dist/server/auth.js +37 -20
  211. package/dist/server/better-auth-migrations.js +23 -0
  212. package/dist/server/email-template.d.ts +5 -0
  213. package/dist/server/email-template.js +9 -0
  214. package/dist/server/embed-route.js +17 -5
  215. package/dist/server/identity-sso-store.d.ts +40 -66
  216. package/dist/server/identity-sso-store.js +187 -150
  217. package/dist/server/identity-sso.d.ts +17 -73
  218. package/dist/server/identity-sso.js +294 -297
  219. package/dist/server/onboarding-html.d.ts +0 -12
  220. package/dist/server/onboarding-html.js +46 -269
  221. package/dist/server/realtime-token.d.ts +1 -1
  222. package/dist/server/release-migrations.js +8 -0
  223. package/dist/server/transcribe-voice.d.ts +1 -1
  224. package/dist/shared/index.d.ts +1 -1
  225. package/dist/shared/index.js +1 -1
  226. package/dist/shared/workspace-app-id.d.ts +2 -0
  227. package/dist/shared/workspace-app-id.js +10 -0
  228. package/dist/sharing/actions/share-resource.d.ts +1 -0
  229. package/dist/sharing/actions/share-resource.js +20 -5
  230. package/dist/templates/chat/.agents/skills/sharing/SKILL.md +1 -1
  231. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +24 -0
  232. package/dist/templates/chat/app/root.tsx +0 -1
  233. package/dist/templates/chat/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  234. package/dist/templates/chat/public/manifest.json +0 -1
  235. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +24 -0
  236. package/dist/templates/default/app/root.tsx +0 -1
  237. package/dist/templates/default/public/manifest.json +0 -1
  238. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +24 -0
  239. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +22 -11
  240. package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +1 -1
  241. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +24 -0
  242. package/docs/content/cross-app-sso.mdx +85 -27
  243. package/docs/content/template-mail.mdx +0 -6
  244. package/package.json +4 -3
  245. package/src/templates/chat/.agents/skills/sharing/SKILL.md +1 -1
  246. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +24 -0
  247. package/src/templates/chat/app/root.tsx +0 -1
  248. package/src/templates/chat/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  249. package/src/templates/chat/public/manifest.json +0 -1
  250. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +24 -0
  251. package/src/templates/default/app/root.tsx +0 -1
  252. package/src/templates/default/public/manifest.json +0 -1
  253. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +24 -0
  254. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +22 -11
  255. package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +1 -1
  256. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +24 -0
  257. package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +0 -6
  258. package/corpus/templates/clips/app/components/meetings/google-oauth-identity-notice.tsx +0 -51
  259. package/corpus/templates/clips/changelog/2026-07-30-google-calendar-warning.md +0 -6
@@ -312,14 +312,20 @@ export function orderChatFirstAppIds(appIds, layout) {
312
312
  ...preferredDefaults,
313
313
  ...appIds.filter((id) => !preferredDefaultSet.has(id)),
314
314
  ];
315
+ const manualOrderedIds = layout.orderedIds.filter((id) => available.has(id));
316
+ const hasManualOrder = manualOrderedIds.length > 0;
315
317
  const ordered = [
316
- ...layout.orderedIds.filter((id) => available.has(id)),
318
+ ...manualOrderedIds,
317
319
  ...fallbackOrder.filter((id) => !layout.orderedIds.includes(id)),
318
320
  ];
319
- const pinned = new Set(layout.pinnedIds.filter((id) => available.has(id)));
320
- // `orderedIds` is the single source of positional truth. Pinning changes
321
- // presentation only; it must not rewrite the user's drag order or make an
322
- // unpinned app jump to the fallback order.
321
+ const pinnedIds = layout.pinnedIds.filter((id) => available.has(id));
322
+ const pinned = new Set(pinnedIds);
323
+ // A live `orderedIds` value is the single source of positional truth. Pinning
324
+ // changes presentation only; it must not rewrite the user's drag order or
325
+ // make an unpinned app jump to the fallback order.
326
+ if (!hasManualOrder) {
327
+ return [...pinnedIds, ...fallbackOrder.filter((id) => !pinned.has(id))];
328
+ }
323
329
  return [
324
330
  ...ordered.filter((id) => pinned.has(id)),
325
331
  ...ordered.filter((id) => !pinned.has(id)),
@@ -46,6 +46,13 @@ export interface GuidedQuestionPayload {
46
46
  * promise with the answer instead of forwarding it to the agent chat.
47
47
  */
48
48
  clientResolveId?: string;
49
+ /**
50
+ * Chat thread that asked. Set by the agent's `ask-question` tool. A payload
51
+ * carrying this only renders in that conversation; one without it (app code
52
+ * calling {@link askUserQuestion}, deterministic writes) is not thread-bound
53
+ * and renders wherever the flow is mounted.
54
+ */
55
+ threadId?: string;
49
56
  }
50
57
  export declare function isOtherGuidedAnswer(value: unknown): value is string;
51
58
  export declare function getOtherGuidedAnswerText(value: unknown): string;
@@ -137,6 +144,13 @@ export interface UseGuidedQuestionFlowOptions {
137
144
  * (which it almost always does — see `sessionBrowserTabId`).
138
145
  */
139
146
  browserTabId?: string;
147
+ /**
148
+ * The conversation this flow is mounted in. Agent-written payloads name the
149
+ * thread that asked, and a pending question belongs to that conversation
150
+ * only — the application-state key is per browser tab, so without this the
151
+ * same card follows the user into every other chat in the tab.
152
+ */
153
+ threadId?: string;
140
154
  queryKey?: readonly unknown[];
141
155
  refetchInterval?: number | false;
142
156
  submitMessage?: string;
@@ -147,7 +161,7 @@ export interface UseGuidedQuestionFlowOptions {
147
161
  }) => string;
148
162
  buildSkipContext?: () => string;
149
163
  }
150
- export declare function useGuidedQuestionFlow({ enabled, stateKey, browserTabId, queryKey, refetchInterval, submitMessage, skipMessage, buildSubmitContext, buildSkipContext, }?: UseGuidedQuestionFlowOptions): {
164
+ export declare function useGuidedQuestionFlow({ enabled, stateKey, browserTabId, threadId, queryKey, refetchInterval, submitMessage, skipMessage, buildSubmitContext, buildSkipContext, }?: UseGuidedQuestionFlowOptions): {
151
165
  payload: GuidedQuestionPayload | null;
152
166
  questions: GuidedQuestion[] | null;
153
167
  title: string | undefined;
@@ -406,7 +406,17 @@ function normalizeBrowserTabId(browserTabId) {
406
406
  const trimmed = browserTabId.trim();
407
407
  return SAFE_BROWSER_TAB_ID_RE.test(trimmed) ? trimmed : undefined;
408
408
  }
409
- export function useGuidedQuestionFlow({ enabled = true, stateKey = "show-questions", browserTabId, queryKey = ["show-questions"], refetchInterval = false, submitMessage = "Here are my answers — go ahead.", skipMessage = "Skip the questions — decide for me.", buildSubmitContext, buildSkipContext, } = {}) {
409
+ /**
410
+ * Whether a stored payload belongs to the conversation currently on screen.
411
+ * Payloads with no `threadId` are not thread-bound and always match.
412
+ */
413
+ function payloadBelongsToThread(payload, threadId) {
414
+ const asker = typeof payload.threadId === "string" ? payload.threadId.trim() : "";
415
+ if (!asker)
416
+ return true;
417
+ return asker === (threadId ?? "").trim();
418
+ }
419
+ export function useGuidedQuestionFlow({ enabled = true, stateKey = "show-questions", browserTabId, threadId, queryKey = ["show-questions"], refetchInterval = false, submitMessage = "Here are my answers — go ahead.", skipMessage = "Skip the questions — decide for me.", buildSubmitContext, buildSkipContext, } = {}) {
410
420
  const queryClient = useQueryClient();
411
421
  const [payload, setPayload] = useState(null);
412
422
  const normalizedBrowserTabId = useMemo(() => normalizeBrowserTabId(browserTabId), [browserTabId]);
@@ -458,7 +468,11 @@ export function useGuidedQuestionFlow({ enabled = true, stateKey = "show-questio
458
468
  if (prev &&
459
469
  guidedQuestionsFingerprint(prev.questions) ===
460
470
  guidedQuestionsFingerprint(data.questions) &&
461
- prev.clientResolveId === data.clientResolveId) {
471
+ prev.clientResolveId === data.clientResolveId &&
472
+ // Two chats can ask a word-for-word identical question. Keeping the
473
+ // old payload would bind the new one to the wrong thread, hiding it
474
+ // in the chat that asked and re-showing it in the one that didn't.
475
+ prev.threadId === data.threadId) {
462
476
  return prev;
463
477
  }
464
478
  return data;
@@ -468,6 +482,10 @@ export function useGuidedQuestionFlow({ enabled = true, stateKey = "show-questio
468
482
  setPayload(null);
469
483
  }
470
484
  }, [data]);
485
+ // A question asked in another conversation stays in application state — the
486
+ // user can still answer it by going back to that chat — but it must not
487
+ // render here.
488
+ const visiblePayload = payload && payloadBelongsToThread(payload, threadId) ? payload : null;
471
489
  const clear = useCallback(() => {
472
490
  setPayload(null);
473
491
  queryClient.setQueryData(resolvedQueryKey, null);
@@ -481,15 +499,15 @@ export function useGuidedQuestionFlow({ enabled = true, stateKey = "show-questio
481
499
  const handleSubmit = useCallback((answers) => {
482
500
  // Client-initiated question (askUserQuestion): resolve the caller's
483
501
  // promise with the answer instead of forwarding it to the agent chat.
484
- const resolveId = payload?.clientResolveId;
502
+ const resolveId = visiblePayload?.clientResolveId;
485
503
  if (resolveId) {
486
- const firstId = payload?.questions?.[0]?.id ?? "q1";
504
+ const firstId = visiblePayload?.questions?.[0]?.id ?? "q1";
487
505
  resolveClientQuestion(resolveId, extractSingleAnswer(answers, firstId));
488
506
  clear();
489
507
  return;
490
508
  }
491
509
  const formattedAnswers = formatGuidedAnswersForAgent(answers);
492
- const resolvedSubmitMessage = payload?.submitMessage ?? submitMessage;
510
+ const resolvedSubmitMessage = visiblePayload?.submitMessage ?? submitMessage;
493
511
  const context = buildSubmitContext?.({ answers, formattedAnswers }) ??
494
512
  defaultGuidedSubmitContext(formattedAnswers);
495
513
  sendToAgentChat({
@@ -498,28 +516,28 @@ export function useGuidedQuestionFlow({ enabled = true, stateKey = "show-questio
498
516
  submit: true,
499
517
  });
500
518
  clear();
501
- }, [buildSubmitContext, clear, payload, submitMessage]);
519
+ }, [buildSubmitContext, clear, visiblePayload, submitMessage]);
502
520
  const handleSkip = useCallback(() => {
503
- const resolveId = payload?.clientResolveId;
521
+ const resolveId = visiblePayload?.clientResolveId;
504
522
  if (resolveId) {
505
523
  resolveClientQuestion(resolveId, null);
506
524
  clear();
507
525
  return;
508
526
  }
509
527
  sendToAgentChat({
510
- message: payload?.skipMessage ?? skipMessage,
528
+ message: visiblePayload?.skipMessage ?? skipMessage,
511
529
  context: buildSkipContext?.(),
512
530
  submit: true,
513
531
  });
514
532
  clear();
515
- }, [buildSkipContext, clear, payload, skipMessage]);
533
+ }, [buildSkipContext, clear, visiblePayload, skipMessage]);
516
534
  return {
517
- payload,
518
- questions: payload?.questions ?? null,
519
- title: payload?.title,
520
- description: payload?.description,
521
- skipLabel: payload?.skipLabel,
522
- submitLabel: payload?.submitLabel,
535
+ payload: visiblePayload,
536
+ questions: visiblePayload?.questions ?? null,
537
+ title: visiblePayload?.title,
538
+ description: visiblePayload?.description,
539
+ skipLabel: visiblePayload?.skipLabel,
540
+ submitLabel: visiblePayload?.submitLabel,
523
541
  clear,
524
542
  handleSubmit,
525
543
  handleSkip,
@@ -4,7 +4,7 @@ export { useDbSync, useFileWatcher, useScreenRefreshKey, } from "../use-db-sync.
4
4
  export { useChangeVersion, useChangeVersions, getChangeVersion, bumpChangeVersion, } from "../use-change-version.js";
5
5
  export { useDemoModeStatus, type DemoModeStatus, } from "../use-demo-mode-status.js";
6
6
  export { useReconciledState } from "../use-external-value.js";
7
- export { useSession, type AuthSession } from "../use-session.js";
7
+ export { notifySessionInvalidated, useSession, type AuthSession, } from "../use-session.js";
8
8
  export { ACTION_KEEPALIVE_BODY_BUDGET_BYTES, callAction, tryCallActionKeepalive, useActionQuery, useActionMutation, type ActionRegistry, type ClientActionCallOptions, type ClientActionMethod, type KeepaliveActionCallRejectionReason, type KeepaliveActionCallResult, } from "../use-action.js";
9
9
  export { createAgentNativeQueryClient } from "../create-query-client.js";
10
10
  export { AppProviders, type AppProvidersProps } from "../app-providers.js";
@@ -4,7 +4,7 @@ export { useDbSync, useFileWatcher, useScreenRefreshKey, } from "../use-db-sync.
4
4
  export { useChangeVersion, useChangeVersions, getChangeVersion, bumpChangeVersion, } from "../use-change-version.js";
5
5
  export { useDemoModeStatus, } from "../use-demo-mode-status.js";
6
6
  export { useReconciledState } from "../use-external-value.js";
7
- export { useSession } from "../use-session.js";
7
+ export { notifySessionInvalidated, useSession, } from "../use-session.js";
8
8
  export { ACTION_KEEPALIVE_BODY_BUDGET_BYTES, callAction, tryCallActionKeepalive, useActionQuery, useActionMutation, } from "../use-action.js";
9
9
  export { createAgentNativeQueryClient } from "../create-query-client.js";
10
10
  export { AppProviders } from "../app-providers.js";
@@ -27,6 +27,8 @@ export interface OrgSwitcherProps {
27
27
  * Pass `null` for apps that do not mount the Agent page.
28
28
  */
29
29
  agentPath?: string | null;
30
+ /** Omit the link to the app that currently owns this switcher. */
31
+ currentAppId?: string;
30
32
  }
31
33
  /**
32
34
  * Compact org switcher button. Shows the active org (or "Personal" when the
@@ -34,4 +36,4 @@ export interface OrgSwitcherProps {
34
36
  * invitations, inline forms to create a new org / invite a teammate, and a
35
37
  * sign-out item. Renders nothing in dev / no-auth mode.
36
38
  */
37
- export declare function OrgSwitcher({ className, hideWhenSingle, reserveSpace, compact, settingsPath, profilePath, agentPath, }: OrgSwitcherProps): import("react").JSX.Element | null;
39
+ export declare function OrgSwitcher({ className, hideWhenSingle, reserveSpace, compact, settingsPath, profilePath, agentPath, currentAppId, }: OrgSwitcherProps): import("react").JSX.Element | null;
@@ -8,7 +8,7 @@ import { shouldOfferWorkspace } from "../../org/workspace-url.js";
8
8
  import { agentNativePath } from "../api-path.js";
9
9
  import { useT } from "../i18n.js";
10
10
  import { useDemoModeStatus } from "../use-demo-mode-status.js";
11
- import { useSession } from "../use-session.js";
11
+ import { notifySessionInvalidated, useSession } from "../use-session.js";
12
12
  import { useOrg, useSwitchOrg, useCreateOrg, useInviteMember, useAcceptInvitation, useJoinByDomain, } from "./hooks.js";
13
13
  import { ORG_SWITCHER_MAX_APP_LINKS, useOrgSwitcherAppLinks, visibleOrgAppLinks, } from "./workspace-app-links.js";
14
14
  function personalLabelFromEmail(email) {
@@ -76,23 +76,28 @@ function AppMenuLink({ app, onNavigate, }) {
76
76
  (app.isDispatch ? "Workspace hub" : `${app.name} workspace`);
77
77
  return (_jsxs("a", { href: app.href, onClick: onNavigate, className: "flex items-center gap-2 rounded-sm px-2.5 py-2 text-xs outline-none hover:bg-accent focus:bg-accent", children: [_jsx("span", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground", children: _jsx(Icon, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate font-medium text-foreground", children: app.name }), _jsx("span", { className: "block truncate text-[11px] text-muted-foreground", title: description, children: description })] }), _jsx(IconArrowUpRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" })] }));
78
78
  }
79
- function AppsSubmenu({ apps, isLoading, dispatchHref, dispatchAllAppsHref, onNavigate, }) {
80
- const { links, overflowCount } = visibleOrgAppLinks(apps);
79
+ function AppsSubmenu({ apps, isLoading, dispatchHref, dispatchAllAppsHref, currentAppId, onNavigate, }) {
80
+ const appsForMenu = currentAppId
81
+ ? apps.filter((app) => app.id !== currentAppId)
82
+ : apps;
83
+ const { links, overflowCount } = visibleOrgAppLinks(appsForMenu);
81
84
  const visibleDispatchApp = links.find((app) => app.isDispatch);
82
- const dispatchApp = visibleDispatchApp ??
83
- {
84
- id: "dispatch",
85
- name: "Dispatch",
86
- href: dispatchHref,
87
- isDispatch: true,
88
- status: "ready",
89
- };
85
+ const dispatchApp = currentAppId === "dispatch"
86
+ ? null
87
+ : (visibleDispatchApp ??
88
+ {
89
+ id: "dispatch",
90
+ name: "Dispatch",
91
+ href: dispatchHref,
92
+ isDispatch: true,
93
+ status: "ready",
94
+ });
90
95
  const visibleNonDispatch = links
91
96
  .filter((app) => !app.isDispatch)
92
- .slice(0, visibleDispatchApp ? undefined : ORG_SWITCHER_MAX_APP_LINKS - 1);
97
+ .slice(0, dispatchApp ? undefined : ORG_SWITCHER_MAX_APP_LINKS);
93
98
  const shownCount = (dispatchApp ? 1 : 0) + visibleNonDispatch.length;
94
- const remainingCount = Math.max(overflowCount, apps.length - shownCount);
95
- return (_jsxs(PopoverPrimitive.Root, { children: [_jsx(PopoverPrimitive.Trigger, { asChild: true, children: _jsxs("button", { type: "button", className: `${ITEM_CLASS} cursor-pointer`, children: [_jsx(IconApps, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsx("span", { className: "flex-1 text-start", children: "Apps" }), _jsx("span", { className: "text-[11px] text-muted-foreground", children: isLoading ? (_jsx(IconLoader2, { className: "h-3 w-3 animate-spin" })) : (apps.length) }), _jsx(IconChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground rtl:-scale-x-100" })] }) }), _jsx(PopoverPrimitive.Portal, { children: _jsxs(PopoverPrimitive.Content, { side: "right", align: "start", sideOffset: 8, collisionPadding: 12, className: APP_SUBMENU_CONTENT_CLASS, children: [_jsx(AppMenuLink, { app: dispatchApp, onNavigate: onNavigate }), visibleNonDispatch.length > 0 && (_jsx("div", { className: "my-1 h-px bg-border" })), visibleNonDispatch.map((app) => (_jsx(AppMenuLink, { app: app, onNavigate: onNavigate }, app.id))), remainingCount > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "my-1 h-px bg-border" }), _jsxs("a", { href: dispatchAllAppsHref, onClick: onNavigate, className: "flex items-center gap-2 rounded-sm px-2.5 py-1.5 text-xs text-foreground outline-none hover:bg-accent focus:bg-accent", children: [_jsx(IconApps, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsx("span", { className: "flex-1", children: `View ${remainingCount} more in Dispatch` }), _jsx(IconArrowUpRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" })] })] }))] }) })] }));
99
+ const remainingCount = Math.max(overflowCount, appsForMenu.length - shownCount);
100
+ return (_jsxs(PopoverPrimitive.Root, { children: [_jsx(PopoverPrimitive.Trigger, { asChild: true, children: _jsxs("button", { type: "button", className: `${ITEM_CLASS} cursor-pointer`, children: [_jsx(IconApps, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsx("span", { className: "flex-1 text-start", children: "Apps" }), _jsx("span", { className: "text-[11px] text-muted-foreground", children: isLoading ? (_jsx(IconLoader2, { className: "h-3 w-3 animate-spin" })) : (appsForMenu.length) }), _jsx(IconChevronRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground rtl:-scale-x-100" })] }) }), _jsx(PopoverPrimitive.Portal, { children: _jsxs(PopoverPrimitive.Content, { side: "right", align: "start", sideOffset: 8, collisionPadding: 12, className: APP_SUBMENU_CONTENT_CLASS, children: [dispatchApp && (_jsx(AppMenuLink, { app: dispatchApp, onNavigate: onNavigate })), dispatchApp && visibleNonDispatch.length > 0 && (_jsx("div", { className: "my-1 h-px bg-border" })), visibleNonDispatch.map((app) => (_jsx(AppMenuLink, { app: app, onNavigate: onNavigate }, app.id))), remainingCount > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "my-1 h-px bg-border" }), _jsxs("a", { href: dispatchAllAppsHref, onClick: onNavigate, className: "flex items-center gap-2 rounded-sm px-2.5 py-1.5 text-xs text-foreground outline-none hover:bg-accent focus:bg-accent", children: [_jsx(IconApps, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsx("span", { className: "flex-1", children: `View ${remainingCount} more in Dispatch` }), _jsx(IconArrowUpRight, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" })] })] }))] }) })] }));
96
101
  }
97
102
  function ReservedOrgSwitcherSpace({ className }) {
98
103
  return _jsx("div", { "aria-hidden": "true", className: `h-8 ${className ?? ""}` });
@@ -106,7 +111,7 @@ function OrgSwitcherLoadingPlaceholder({ className }) {
106
111
  * invitations, inline forms to create a new org / invite a teammate, and a
107
112
  * sign-out item. Renders nothing in dev / no-auth mode.
108
113
  */
109
- export function OrgSwitcher({ className, hideWhenSingle, reserveSpace, compact, settingsPath = DEFAULT_ORGANIZATION_SETTINGS_PATH, profilePath = DEFAULT_PROFILE_PATH, agentPath = DEFAULT_AGENT_PATH, }) {
114
+ export function OrgSwitcher({ className, hideWhenSingle, reserveSpace, compact, settingsPath = DEFAULT_ORGANIZATION_SETTINGS_PATH, profilePath = DEFAULT_PROFILE_PATH, agentPath = DEFAULT_AGENT_PATH, currentAppId, }) {
110
115
  const { data: org, isLoading } = useOrg();
111
116
  const { session } = useSession();
112
117
  const { enabled: demoModeEnabled } = useDemoModeStatus();
@@ -145,6 +150,7 @@ export function OrgSwitcher({ className, hideWhenSingle, reserveSpace, compact,
145
150
  catch {
146
151
  /* fall through to reload — server may already have cleared the cookie */
147
152
  }
153
+ notifySessionInvalidated();
148
154
  window.location.reload();
149
155
  };
150
156
  if (!org) {
@@ -216,7 +222,7 @@ export function OrgSwitcher({ className, hideWhenSingle, reserveSpace, compact,
216
222
  setJoiningOrgId(null);
217
223
  }
218
224
  }, disabled: joinByDomain.isPending, className: "rounded px-1.5 py-0.5 text-[11px] font-medium text-primary hover:bg-primary/10 disabled:opacity-50 cursor-pointer", children: isJoining ? (_jsx(IconLoader2, { className: "h-3 w-3 animate-spin" })) : ("Join") })] }, match.orgId));
219
- })] })), _jsx("div", { className: "my-1 h-px bg-border" }), _jsx(AppsSubmenu, { apps: appLinks.apps, isLoading: appLinks.isLoading, dispatchHref: appLinks.dispatchHref, dispatchAllAppsHref: appLinks.dispatchAllAppsHref, onNavigate: () => setOpen(false) }), profilePath && (_jsxs("button", { type: "button", onClick: () => {
225
+ })] })), _jsx("div", { className: "my-1 h-px bg-border" }), _jsx(AppsSubmenu, { apps: appLinks.apps, isLoading: appLinks.isLoading, dispatchHref: appLinks.dispatchHref, dispatchAllAppsHref: appLinks.dispatchAllAppsHref, currentAppId: currentAppId, onNavigate: () => setOpen(false) }), profilePath && (_jsxs("button", { type: "button", onClick: () => {
220
226
  setOpen(false);
221
227
  navigate(profilePath);
222
228
  }, className: `${ITEM_CLASS} cursor-pointer`, children: [_jsx(IconUserCircle, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsx("span", { className: "flex-1 text-start", children: t("settings.profileMenuItem") })] })), agentPath && (_jsxs("button", { type: "button", onClick: () => {
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { IconExternalLink } from "@tabler/icons-react";
3
3
  import { useState } from "react";
4
- import { shouldOfferWorkspace } from "../../org/workspace-url.js";
4
+ import { isLocalDevelopmentOrigin, shouldOfferWorkspace, } from "../../org/workspace-url.js";
5
5
  import { useOrg } from "./hooks.js";
6
6
  const DISMISS_KEY_PREFIX = "agent-native:workspace-notice-dismissed:";
7
7
  function dismissKey(workspaceUrl) {
@@ -38,6 +38,8 @@ export function WorkspaceNotice({ className }) {
38
38
  const workspaceUrl = org?.workspaceUrl ?? null;
39
39
  if (typeof window === "undefined")
40
40
  return null;
41
+ if (isLocalDevelopmentOrigin(window.location.href))
42
+ return null;
41
43
  if (!workspaceUrl || dismissedNow || readDismissed(workspaceUrl))
42
44
  return null;
43
45
  if (!shouldOfferWorkspace(window.location.href, workspaceUrl))
@@ -7,4 +7,4 @@ export * from "./use-builtin-capabilities.js";
7
7
  export { DEFAULT_MCP_INTEGRATIONS, filterMcpIntegrations, findMcpIntegrationForText, getDefaultMcpIntegrations, isCustomMcpIntegrationEnabled, isMcpIntegrationCatalogAvailable, mergeDefaultMcpIntegrations, type DefaultMcpIntegration, } from "./mcp-integration-catalog.js";
8
8
  export { McpIntegrationDialog, type McpIntegrationDialogProps, } from "./McpIntegrationDialog.js";
9
9
  export { McpConnectionSuggestion, type McpConnectionSuggestionProps, type McpConnectionSuggestionVariant, } from "./McpConnectionSuggestion.js";
10
- export { McpServersApiProvider, useMcpServersApi, useMcpServers, useCreateMcpServer, useDeleteMcpServer, useReconnectMcpServer, testMcpServerUrl, type CreateMcpServerArgs, type McpServer, type McpServerScope, type McpServersApi, type McpServersList, type ReconnectMcpServerArgs, type TestMcpUrlResult, } from "./use-mcp-servers.js";
10
+ export { McpServersApiProvider, useMcpServersApi, useMcpServers, useCreateMcpServer, useDeleteMcpServer, useReconnectMcpServer, testMcpServerUrl, formatMcpServersLoadError, type CreateMcpServerArgs, type McpServer, type McpServerScope, type McpServersApi, type McpServersList, type ReconnectMcpServerArgs, type TestMcpUrlResult, } from "./use-mcp-servers.js";
@@ -7,4 +7,4 @@ export * from "./use-builtin-capabilities.js";
7
7
  export { DEFAULT_MCP_INTEGRATIONS, filterMcpIntegrations, findMcpIntegrationForText, getDefaultMcpIntegrations, isCustomMcpIntegrationEnabled, isMcpIntegrationCatalogAvailable, mergeDefaultMcpIntegrations, } from "./mcp-integration-catalog.js";
8
8
  export { McpIntegrationDialog, } from "./McpIntegrationDialog.js";
9
9
  export { McpConnectionSuggestion, } from "./McpConnectionSuggestion.js";
10
- export { McpServersApiProvider, useMcpServersApi, useMcpServers, useCreateMcpServer, useDeleteMcpServer, useReconnectMcpServer, testMcpServerUrl, } from "./use-mcp-servers.js";
10
+ export { McpServersApiProvider, useMcpServersApi, useMcpServers, useCreateMcpServer, useDeleteMcpServer, useReconnectMcpServer, testMcpServerUrl, formatMcpServersLoadError, } from "./use-mcp-servers.js";
@@ -83,6 +83,7 @@ export interface TestMcpUrlResult {
83
83
  }
84
84
  export declare function getMcpUrlValidationError(rawUrl: string): string | null;
85
85
  export declare function formatMcpServerError(error: unknown): string;
86
+ export declare function formatMcpServersLoadError(error: unknown): string;
86
87
  export declare function testMcpServerUrl(url: string, headers?: Record<string, string>): Promise<TestMcpUrlResult>;
87
88
  /**
88
89
  * Virtual tree-node id used when a server is surfaced in the Workspace tree.
@@ -178,6 +178,20 @@ export function formatMcpServerError(error) {
178
178
  }
179
179
  return text.length > 240 ? `${text.slice(0, 237).trimEnd()}...` : text;
180
180
  }
181
+ export function formatMcpServersLoadError(error) {
182
+ const raw = typeof error === "string"
183
+ ? error
184
+ : error instanceof Error
185
+ ? error.message
186
+ : String(error ?? "");
187
+ const text = raw.trim();
188
+ if (!text)
189
+ return "Could not load agent integrations.";
190
+ if (/401|unauthorized|signed-in workspace app|workspace app/i.test(text)) {
191
+ return "Sign in to a workspace app, then retry loading agent integrations.";
192
+ }
193
+ return text.length > 240 ? `${text.slice(0, 237).trimEnd()}...` : text;
194
+ }
181
195
  export async function testMcpServerUrl(url, headers) {
182
196
  const validationError = getMcpUrlValidationError(url);
183
197
  if (validationError)
@@ -107,11 +107,17 @@ export function AccountSettingsForm({ compact = false, }) {
107
107
  const [uploading, setUploading] = useState(false);
108
108
  const [photoStatus, setPhotoStatus] = useState("idle");
109
109
  const [name, setName] = useState("");
110
+ const nameEditedRef = useRef(false);
110
111
  const displayName = profileQuery.data?.name ||
111
112
  session?.name ||
112
113
  email ||
113
114
  t("settings.profileSignedOut");
114
115
  useEffect(() => {
116
+ nameEditedRef.current = false;
117
+ }, [email]);
118
+ useEffect(() => {
119
+ if (nameEditedRef.current)
120
+ return;
115
121
  const nextName = profileQuery.data?.name || session?.name;
116
122
  if (nextName)
117
123
  setName(nextName);
@@ -144,12 +150,10 @@ export function AccountSettingsForm({ compact = false, }) {
144
150
  return (_jsxs(SettingsGroup, { id: "account", title: t("settings.profileTitle"), description: t("settings.profileDescription"), className: cn(compact && "[&>div:last-child>div>div]:py-3"), children: [_jsx(SettingsRow, { id: "profile", label: t("settings.profileTitle"), description: profileStatus, control: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Avatar, { name: displayName, src: avatarUrl, fallback: profileInitials(displayName), size: "default", className: "size-10 shrink-0 rounded-full border border-border bg-accent font-semibold text-muted-foreground" }), _jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", className: "hidden", onChange: handleAvatarChange }), _jsx(ActionButton, { type: "button", intent: "neutral", emphasis: "outline", size: "compact", disabled: !email || uploading, leadingIcon: _jsx(IconCamera, { className: "size-3.5" }), onPress: () => fileInputRef.current?.click(), children: uploading
145
151
  ? t("settings.profileUploading")
146
152
  : t("settings.profileChangePhoto") })] }) }), _jsx(SettingsRow, { id: "profile-name", label: t("settings.profileNameLabel"), description: updateProfile.isSuccess ? (_jsx("span", { className: "text-primary", children: t("settings.profileSaved") })) : updateProfile.error ? (_jsx("span", { className: "text-destructive", children: t("settings.profileSaveError") })) : (t("settings.profileNameDescription")), control: _jsxs("div", { className: "flex w-full items-center gap-2 sm:w-80", children: [_jsx(TextField, { id: "agent-native-profile-name", value: name, onChange: (value) => {
153
+ nameEditedRef.current = true;
147
154
  updateProfile.reset();
148
155
  setName(value);
149
- }, placeholder: t("settings.profileNamePlaceholder"), disabled: !email || profileQuery.isLoading || updateProfile.isPending, "aria-label": t("settings.profileNameLabel"), className: "min-w-0 flex-1" }), _jsx(ActionButton, { type: "button", intent: "primary", emphasis: "solid", size: "compact", pending: updateProfile.isPending, disabled: !email ||
150
- profileQuery.isLoading ||
151
- updateProfile.isPending ||
152
- !name.trim(), onPress: handleProfileSave, children: updateProfile.isPending
156
+ }, placeholder: t("settings.profileNamePlaceholder"), disabled: !email || updateProfile.isPending, "aria-label": t("settings.profileNameLabel"), className: "min-w-0 flex-1" }), _jsx(ActionButton, { type: "button", intent: "primary", emphasis: "solid", size: "compact", pending: updateProfile.isPending, disabled: !email || updateProfile.isPending || !name.trim(), onPress: handleProfileSave, children: updateProfile.isPending
153
157
  ? t("settings.profileSaving")
154
158
  : t("settings.profileSave") })] }) }), _jsx(SettingsRow, { id: "timezone", label: t("settings.timezoneLabel", { defaultValue: "Timezone" }), description: t("settings.timezoneHint", {
155
159
  defaultValue: "Used for timestamps and scheduled automations.",
@@ -8,7 +8,7 @@ import { PROVIDER_ENV_PLACEHOLDERS } from "../../agent/engine/provider-env-vars.
8
8
  import { buildSettingsRoute } from "../../navigation/index.js";
9
9
  import { saveAgentEngineProviderSettings, setAgentEngineProvider, } from "../agent-engine-key.js";
10
10
  import { AgentWorkspaceContent } from "../agent-page/AgentWorkspaceContent.js";
11
- import { getAgentProviderOption, providerIdForEngine, } from "../agent-provider-catalog.js";
11
+ import { AGENT_PROVIDER_CATALOG, getAgentProviderOption, providerIdForEngine, } from "../agent-provider-catalog.js";
12
12
  import { agentNativePath } from "../api-path.js";
13
13
  import { BuilderBMark } from "../builder-mark.js";
14
14
  import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
@@ -208,8 +208,6 @@ function UseBuilderCard({ builderFlow, connectUrl, connected, orgName, envManage
208
208
  ? "Connect account"
209
209
  : "Reconnect" })), credentialSource !== "env" ? _jsx(DisconnectBuilderButton, {}) : null] })) : null] }));
210
210
  }
211
- if (!connectUrl)
212
- return null;
213
211
  if (compact) {
214
212
  return (_jsxs(Button, { type: "button", intent: "primary", emphasis: "solid", onClick: () => builderFlow.start({ trackingSource, trackingFlow }), disabled: builderFlow.connecting, className: "inline-flex shrink-0 items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:cursor-wait disabled:opacity-70", children: [builderFlow.connecting ? "Connecting…" : "Connect Builder.io", builderFlow.connecting ? (_jsx(IconLoader2, { size: 14, className: "animate-spin" })) : null] }));
215
213
  }
@@ -278,6 +276,9 @@ function computeSourceBadge(args) {
278
276
  if (settingsStatus?.source === "env") {
279
277
  return `Connected via ${settingsStatus.envVar ?? args.envVar ?? "env"}`;
280
278
  }
279
+ if (settingsStatus?.source === "app_secrets") {
280
+ return "Connected via saved key";
281
+ }
281
282
  return "Connected via template (server-side)";
282
283
  }
283
284
  if (args.envConfigured)
@@ -373,7 +374,9 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
373
374
  setBaseUrlConfigured(Boolean(data?.openAiBaseUrlConfigured));
374
375
  if (data?.configured &&
375
376
  typeof data.engine === "string" &&
376
- (data.source === "env" || data.source === "settings")) {
377
+ (data.source === "env" ||
378
+ data.source === "settings" ||
379
+ data.source === "app_secrets")) {
377
380
  setSettingsStatus({
378
381
  engine: data.engine,
379
382
  source: data.source,
@@ -407,15 +410,40 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
407
410
  .finally(() => setEnginesLoaded(true));
408
411
  }, []);
409
412
  const selectedEngineInfo = engines.find((e) => e.name === selectedEngine);
410
- const envVar = selectedEngineInfo?.requiredEnvVars?.[0];
413
+ const selectedProvider = providerIdForEngine(selectedEngine) ?? "anthropic";
414
+ const selectedProviderOption = getAgentProviderOption(selectedProvider);
415
+ const envVar = selectedProviderOption.key;
411
416
  const selectedEnginePackageInstalled = selectedEngineInfo?.packageInstalled !== false;
412
417
  const envConfigured = envVar
413
418
  ? (envKeys.find((k) => k.key === envVar)?.configured ?? false)
414
419
  : false;
415
- const settingsConfigured = settingsStatus != null && settingsStatus.engine === currentEngine;
420
+ const settingsConfigured = settingsStatus != null &&
421
+ (settingsStatus.engine === selectedEngine ||
422
+ (!!envVar && settingsStatus.envVar === envVar));
423
+ const configuredProviderIds = useMemo(() => {
424
+ const configured = new Set();
425
+ for (const option of AGENT_PROVIDER_CATALOG) {
426
+ if (option.key &&
427
+ envKeys.some((entry) => entry.key === option.key && entry.configured)) {
428
+ configured.add(option.id);
429
+ }
430
+ }
431
+ if (settingsStatus) {
432
+ const statusProvider = providerIdForEngine(settingsStatus.engine);
433
+ if (statusProvider)
434
+ configured.add(statusProvider);
435
+ if (settingsStatus.envVar) {
436
+ const statusOption = AGENT_PROVIDER_CATALOG.find((option) => option.key === settingsStatus.envVar);
437
+ if (statusOption)
438
+ configured.add(statusOption.id);
439
+ }
440
+ }
441
+ return configured;
442
+ }, [envKeys, settingsStatus]);
416
443
  const builderConnected = connected || builderFlow.configured;
417
444
  const anyKeyConfigured = builderConnected ||
418
- (selectedEnginePackageInstalled && (envConfigured || settingsConfigured));
445
+ (selectedEnginePackageInstalled &&
446
+ (envConfigured || settingsConfigured || configuredProviderIds.size > 0));
419
447
  const sourceBadge = computeSourceBadge({
420
448
  settingsConfigured,
421
449
  settingsStatus,
@@ -425,8 +453,6 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
425
453
  });
426
454
  const manualSetupHint = "Select a provider.";
427
455
  const engineChanged = selectedEngine !== currentEngine || selectedModel !== currentModel;
428
- const selectedProvider = providerIdForEngine(selectedEngine) ?? "anthropic";
429
- const selectedProviderOption = getAgentProviderOption(selectedProvider);
430
456
  const isEndpointProvider = selectedProviderOption.supportsEndpoint === true;
431
457
  const endpointChanged = isEndpointProvider && (!!baseUrl.trim() || clearBaseUrl);
432
458
  const providerSettingsChanged = !!apiKey.trim() || endpointChanged;
@@ -553,7 +579,7 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
553
579
  ? "Manage"
554
580
  : t("agentPanel.addOwnKeys", {
555
581
  defaultValue: "Custom keys",
556
- }), children: _jsxs("div", { className: "space-y-2 mb-1", children: [_jsx(AgentProviderPicker, { value: selectedProvider, layout: isPage ? "page" : "compact", onChange: (provider) => {
582
+ }), children: _jsxs("div", { className: "space-y-2 mb-1", children: [_jsx(AgentProviderPicker, { value: selectedProvider, configuredProviders: configuredProviderIds, layout: isPage ? "page" : "compact", onChange: (provider) => {
557
583
  const option = getAgentProviderOption(provider);
558
584
  setSelectedEngine(option.engine);
559
585
  setSelectedModel(option.defaultModel);
@@ -578,7 +604,10 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
578
604
  setClearBaseUrl(checked);
579
605
  if (checked)
580
606
  setBaseUrl("");
581
- }, "aria-label": "Clear saved endpoint override", className: "shrink-0" }), "Clear saved endpoint override"] })), endpointChanged && (_jsx(Button, { type: "button", intent: "neutral", emphasis: "solid", onClick: handleSave, disabled: saving, className: "rounded bg-accent px-2.5 py-1 text-[10px] font-medium text-foreground hover:bg-accent/80 disabled:opacity-40", children: saving ? (_jsx(IconLoader2, { size: 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: 10 })) : ("Save endpoint") }))] }))] })), envVar && envConfigured ? (_jsxs("div", { className: cn("flex items-center gap-1.5 text-primary", isPage ? "text-xs" : "text-[10px]"), children: [_jsx(IconCheck, { size: isPage ? 14 : 10 }), envVar, " configured"] })) : envVar ? (_jsxs("div", { className: "flex gap-1.5", children: [_jsx("input", { type: "password", value: apiKey, onChange: (e) => setApiKey(e.target.value), onKeyDown: (e) => {
607
+ }, "aria-label": "Clear saved endpoint override", className: "shrink-0" }), "Clear saved endpoint override"] })), endpointChanged && (_jsx(Button, { type: "button", intent: "neutral", emphasis: "solid", onClick: handleSave, disabled: saving, className: "rounded bg-accent px-2.5 py-1 text-[10px] font-medium text-foreground hover:bg-accent/80 disabled:opacity-40", children: saving ? (_jsx(IconLoader2, { size: 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: 10 })) : ("Save endpoint") }))] }))] })), envVar && (envConfigured || settingsConfigured) ? (_jsxs("div", { className: cn("flex items-center gap-1.5 text-primary", isPage ? "text-xs" : "text-[10px]"), children: [_jsx(IconCheck, { size: isPage ? 14 : 10 }), settingsStatus?.source === "app_secrets" &&
608
+ settingsConfigured
609
+ ? "Saved key configured"
610
+ : `${envVar} configured`] })) : envVar ? (_jsxs("div", { className: "flex gap-1.5", children: [_jsx("input", { type: "password", value: apiKey, onChange: (e) => setApiKey(e.target.value), onKeyDown: (e) => {
582
611
  if (e.key === "Enter")
583
612
  handleSave();
584
613
  }, placeholder: PROVIDER_ENV_PLACEHOLDERS[envVar] ?? "...", className: cn(textInputClass(isPage), "flex-1"), style: isPage ? CONTROL_STYLE_PAGE : undefined }), _jsx(Button, { intent: "primary", emphasis: "solid", onClick: handleSave, disabled: !providerSettingsChanged || saving, className: pillButtonClass(isPage, "solid"), children: saving ? (_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: isPage ? 14 : 10 })) : ("Save") })] })) : null, _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { intent: "neutral", emphasis: "outline", onClick: handleTest, disabled: testing, className: pillButtonClass(isPage, "outline"), children: testing ? (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" }), "Testing\u2026"] })) : ("Test") }), PROVIDER_DOCS[selectedEngine] ? (_jsxs("a", { href: PROVIDER_DOCS[selectedEngine], target: "_blank", rel: "noopener noreferrer", className: cn(pillButtonClass(isPage, "outline"), "no-underline"), children: ["Get an API key", _jsx(IconExternalLink, { size: isPage ? 14 : 10 })] })) : null, engineChanged && (_jsx(Button, { intent: "primary", emphasis: "solid", onClick: handleApply, className: pillButtonClass(isPage, "solid"), children: "Apply" })), settingsStatus != null && (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { intent: "danger", emphasis: "outline", onClick: handleDisconnect, className: cn(pillButtonClass(isPage, "outline"), "ms-auto text-muted-foreground hover:bg-destructive/10 hover:text-destructive hover:border-destructive/40"), children: "Disconnect" }) }), _jsx(TooltipContent, { children: "Clear the saved engine \u2014 the app will fall back to the default until you re-apply." })] }))] }), testResult && testResult.ok && (_jsxs("p", { className: cn("flex items-center gap-1 text-primary", isPage ? "text-xs" : "text-[10px]"), children: [_jsx(IconCheck, { size: isPage ? 14 : 10 }), "Test passed \u2014 ", testResult.latencyMs, "ms"] })), testResult && testResult.ok === false && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Test failed: ", testResult.error] })), disconnectError && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Disconnect failed: ", disconnectError] })), applyError && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Apply failed: ", applyError] })), applyNote && (_jsx("p", { className: cn("text-muted-foreground", isPage ? "text-xs" : "text-[10px]"), children: "Changes take effect on next conversation" }))] }) })] })] })) }));
@@ -667,6 +696,8 @@ function AppModelDefaultsSectionInner({ grouped = false, open, onToggle, }) {
667
696
  };
668
697
  }, []);
669
698
  useEffect(() => load(), [load]);
699
+ if (!loading && !settings)
700
+ return null;
670
701
  const selectedEngineInfo = settings?.engines.find((engine) => engine.name === selectedEngine) ?? null;
671
702
  const engineOptions = (settings?.engines ?? [])
672
703
  .filter((engine) => engine.name === selectedEngine || engine.name !== "ai-sdk:anthropic")
@@ -793,7 +824,7 @@ function AppModelDefaultsSectionInner({ grouped = false, open, onToggle, }) {
793
824
  }, onEnter: () => {
794
825
  if (hasPendingChange)
795
826
  void save();
796
- } }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Button, { type: "button", intent: "primary", emphasis: "solid", onClick: () => void save(), disabled: !hasPendingChange || saving, className: pillButtonClass(isPage, "solid"), children: saving ? (_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: isPage ? 14 : 10 })) : ("Save") }), _jsx(Button, { type: "button", intent: "neutral", emphasis: "outline", onClick: reset, disabled: !settings.canUpdate || !hasAppDefault || saving, className: pillButtonClass(isPage, "outline"), children: "Reset" })] })] }), !settings.canUpdate && (_jsx("p", { className: cn("mt-2 text-muted-foreground", noteTextClass(isPage)), children: "Only organization owners and admins can change app model defaults." })), selectedEngineInfo?.packageInstalled === false ? (_jsx("p", { className: cn("mt-2 text-muted-foreground", noteTextClass(isPage)), children: "This app does not include the optional runtime packages for this provider." })) : selectedEngineInfo && !selectedEngineInfo.configured ? (_jsx("p", { className: cn("mt-2 text-muted-foreground", noteTextClass(isPage)), children: "Credentials for this provider were not detected; runtime will fall back if the model cannot be used." })) : null, error && (_jsx("p", { className: cn("mt-2 text-destructive", noteTextClass(isPage)), children: error }))] }) }))) : (_jsx("p", { className: cn("text-muted-foreground", noteTextClass(isPage)), children: "App model defaults are unavailable." })) }));
827
+ } }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Button, { type: "button", intent: "primary", emphasis: "solid", onClick: () => void save(), disabled: !hasPendingChange || saving, className: pillButtonClass(isPage, "solid"), children: saving ? (_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: isPage ? 14 : 10 })) : ("Save") }), _jsx(Button, { type: "button", intent: "neutral", emphasis: "outline", onClick: reset, disabled: !settings.canUpdate || !hasAppDefault || saving, className: pillButtonClass(isPage, "outline"), children: "Reset" })] })] }), !settings.canUpdate && (_jsx("p", { className: cn("mt-2 text-muted-foreground", noteTextClass(isPage)), children: "Only organization owners and admins can change app model defaults." })), selectedEngineInfo?.packageInstalled === false ? (_jsx("p", { className: cn("mt-2 text-muted-foreground", noteTextClass(isPage)), children: "This app does not include the optional runtime packages for this provider." })) : selectedEngineInfo && !selectedEngineInfo.configured ? (_jsx("p", { className: cn("mt-2 text-muted-foreground", noteTextClass(isPage)), children: "Credentials for this provider were not detected; runtime will fall back if the model cannot be used." })) : null, error && (_jsx("p", { className: cn("mt-2 text-destructive", noteTextClass(isPage)), children: error }))] }) }))) : null }));
797
828
  }
798
829
  // ─── Email Section ──────────────────────────────────────────────────────────
799
830
  function EmailSectionInner({ open, onToggle, }) {
@@ -920,6 +951,8 @@ function AgentLimitsSectionInner({ grouped = false, open, onToggle, }) {
920
951
  window.addEventListener("agent-loop-settings:changed", handler);
921
952
  return () => window.removeEventListener("agent-loop-settings:changed", handler);
922
953
  }, []);
954
+ if (!loading && !settings)
955
+ return null;
923
956
  const numericValue = Number(value);
924
957
  const hasPendingChange = !!settings &&
925
958
  settings.canUpdate &&
@@ -1004,7 +1037,7 @@ function AgentLimitsSectionInner({ grouped = false, open, onToggle, }) {
1004
1037
  void save();
1005
1038
  }, className: cn(textInputClass(isPage), "min-w-0 flex-1 disabled:opacity-60"), style: isPage ? CONTROL_STYLE_PAGE : undefined }), _jsx(Button, { type: "button", intent: "primary", emphasis: "solid", onClick: save, disabled: !hasPendingChange || saving, className: pillButtonClass(isPage, "solid"), children: saving ? (_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: isPage ? 14 : 10 })) : ("Save") }), _jsx(Button, { type: "button", intent: "neutral", emphasis: "outline", onClick: reset, disabled: !settings.canUpdate ||
1006
1039
  saving ||
1007
- settings.maxIterations === settings.defaultMaxIterations, className: pillButtonClass(isPage, "outline"), children: "Reset" })] }), !settings.canUpdate && (_jsx("p", { className: cn("mt-2 text-muted-foreground", noteTextClass(isPage)), children: "Only organization owners and admins can change this limit." })), error && (_jsx("p", { className: cn("mt-2 text-destructive", noteTextClass(isPage)), children: error }))] }) }))) : (_jsx("p", { className: cn("text-muted-foreground", noteTextClass(isPage)), children: "Agent limit settings are unavailable." })) }));
1040
+ settings.maxIterations === settings.defaultMaxIterations, className: pillButtonClass(isPage, "outline"), children: "Reset" })] }), !settings.canUpdate && (_jsx("p", { className: cn("mt-2 text-muted-foreground", noteTextClass(isPage)), children: "Only organization owners and admins can change this limit." })), error && (_jsx("p", { className: cn("mt-2 text-destructive", noteTextClass(isPage)), children: error }))] }) }))) : null }));
1008
1041
  }
1009
1042
  export function areExtensionSettingsEnabled(options = {}) {
1010
1043
  return options.extensionTools === true;
@@ -29,9 +29,6 @@ function normalizeTabId(value) {
29
29
  if (normalized === "workspace" || normalized === "workspace-settings") {
30
30
  return "workspace";
31
31
  }
32
- if (normalized === "connections") {
33
- return "integrations";
34
- }
35
32
  return normalized;
36
33
  }
37
34
  function resolveTabId(tabs, value) {
@@ -40,6 +37,14 @@ function resolveTabId(tabs, value) {
40
37
  return null;
41
38
  if (tabs.some((tab) => tab.id === normalized))
42
39
  return normalized;
40
+ const alternateTabId = normalized === "connections"
41
+ ? "integrations"
42
+ : normalized === "integrations"
43
+ ? "connections"
44
+ : null;
45
+ if (alternateTabId && tabs.some((tab) => tab.id === alternateTabId)) {
46
+ return alternateTabId;
47
+ }
43
48
  const nestedTab = tabs
44
49
  .filter((tab) => normalized.startsWith(`${tab.id}:`) ||
45
50
  tab.id.startsWith(`${normalized}:`))
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
- import { type ShareButtonVisibility } from "./useShareButtonController.js";
2
+ import { type ShareButtonRole, type ShareButtonVisibility } from "./useShareButtonController.js";
3
3
  export interface ShareButtonProps {
4
4
  resourceType: string;
5
5
  resourceId: string;
@@ -61,6 +61,11 @@ export interface ShareButtonProps {
61
61
  label?: string;
62
62
  description?: string;
63
63
  }>>;
64
+ /** Optional template-specific labels and descriptions for share roles. */
65
+ roleCopy?: Partial<Record<Role, {
66
+ label?: string;
67
+ description?: string;
68
+ }>>;
64
69
  /** Optional label for the explicit per-person access list. */
65
70
  peopleAccessLabel?: ReactNode;
66
71
  /** Optional label for the coarse visibility control. */
@@ -95,6 +100,7 @@ export interface ShareButtonProps {
95
100
  popoverClassName?: string;
96
101
  }
97
102
  type Visibility = ShareButtonVisibility;
103
+ type Role = ShareButtonRole;
98
104
  /**
99
105
  * Framework share control. Renders a shadcn-outline-styled trigger that
100
106
  * opens a Google-Docs-style popover anchored beneath it. Uses Tailwind