@agent-native/core 0.151.2 → 0.152.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) 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/DocumentEditor.tsx +9 -0
  62. package/corpus/templates/content/app/i18n-data.ts +58 -0
  63. package/corpus/templates/content/app/lib/content-landing.ts +22 -0
  64. package/corpus/templates/content/app/root.tsx +0 -1
  65. package/corpus/templates/content/app/routes/_app._index.tsx +36 -26
  66. package/corpus/templates/content/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  67. package/corpus/templates/content/changelog/2026-08-11-content-opens-your-last-page-on-return-and-gives-new-users-a.md +6 -0
  68. package/corpus/templates/content/docs/product/capabilities/content.workspace.session-restore.md +7 -1
  69. package/corpus/templates/content/parity/matrix.md +1 -0
  70. package/corpus/templates/content/parity/matrix.ts +22 -0
  71. package/corpus/templates/content/public/manifest.json +0 -1
  72. package/corpus/templates/content/shared/content-landing.ts +17 -0
  73. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +1 -1
  74. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +24 -0
  75. package/corpus/templates/crm/app/root.tsx +0 -1
  76. package/corpus/templates/crm/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  77. package/corpus/templates/crm/public/manifest.json +0 -1
  78. package/corpus/templates/design/.agents/skills/sharing/SKILL.md +1 -1
  79. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +24 -0
  80. package/corpus/templates/design/app/i18n/en-US.ts +5 -0
  81. package/corpus/templates/design/app/root.tsx +0 -1
  82. package/corpus/templates/design/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  83. package/corpus/templates/design/changelog/2026-08-11-design-sharing-now-identifies-viewers-who-can-add-review-com.md +6 -0
  84. package/corpus/templates/design/public/manifest.json +0 -1
  85. package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +1 -1
  86. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +24 -0
  87. package/corpus/templates/dispatch/AGENTS.md +9 -0
  88. package/corpus/templates/dispatch/actions/list-dispatch-usage-metrics.ts +5 -19
  89. package/corpus/templates/dispatch/actions/view-screen.ts +10 -3
  90. package/corpus/templates/dispatch/app/root.tsx +0 -1
  91. package/corpus/templates/dispatch/app/routes/settings.tsx +13 -1
  92. package/corpus/templates/dispatch/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  93. package/corpus/templates/dispatch/changelog/2026-08-11-clicking-the-dispatch-logo-returns-to-the-overview-page.md +6 -0
  94. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-no-longer-links-to-itself-from-the-app-switcher.md +6 -0
  95. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-settings-now-link-directly-to-admin.md +6 -0
  96. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-shows-usage-trends-recent-prompts-and-an-agent-revi.md +6 -0
  97. package/corpus/templates/dispatch/changelog/2026-08-11-human-friendly-app-names.md +6 -0
  98. package/corpus/templates/dispatch/public/manifest.json +0 -1
  99. package/corpus/templates/dispatch/server/lib/identity-sso.ts +432 -158
  100. package/corpus/templates/dispatch/server/plugins/identity-sso.ts +204 -123
  101. package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +1 -0
  102. package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +1 -1
  103. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +24 -0
  104. package/corpus/templates/factory/app/root.tsx +0 -1
  105. package/corpus/templates/factory/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  106. package/corpus/templates/factory/public/manifest.json +0 -1
  107. package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +1 -1
  108. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +24 -0
  109. package/corpus/templates/forms/app/root.tsx +0 -1
  110. package/corpus/templates/forms/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  111. package/corpus/templates/forms/public/manifest.json +0 -1
  112. package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +1 -1
  113. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +24 -0
  114. package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +1 -1
  115. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +24 -0
  116. package/corpus/templates/mail/app/root.tsx +0 -1
  117. package/corpus/templates/mail/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  118. package/corpus/templates/mail/changelog/2026-08-11-google-sign-in-now-opens-the-gmail-connection-flow-directly.md +6 -0
  119. package/corpus/templates/mail/public/manifest.json +0 -1
  120. package/corpus/templates/mail/server/plugins/auth.ts +0 -10
  121. package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +1 -1
  122. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +24 -0
  123. package/corpus/templates/plan/app/i18n/en-US.ts +2 -0
  124. package/corpus/templates/plan/app/pages/PlansPage.tsx +6 -0
  125. package/corpus/templates/plan/app/root.tsx +0 -1
  126. package/corpus/templates/plan/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  127. package/corpus/templates/plan/changelog/2026-08-11-plan-sharing-now-identifies-viewers-who-can-add-comments.md +6 -0
  128. package/corpus/templates/plan/public/manifest.json +0 -1
  129. package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +1 -1
  130. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +24 -0
  131. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -0
  132. package/corpus/templates/slides/app/i18n/en-US.ts +2 -0
  133. package/corpus/templates/slides/app/root.tsx +0 -1
  134. package/corpus/templates/slides/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  135. package/corpus/templates/slides/changelog/2026-08-11-share-dialog-uses-commenter-terminology.md +6 -0
  136. package/corpus/templates/slides/public/manifest.json +0 -1
  137. package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +1 -1
  138. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +24 -0
  139. package/corpus/templates/tasks/app/root.tsx +0 -1
  140. package/corpus/templates/tasks/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  141. package/corpus/templates/tasks/public/manifest.json +0 -1
  142. package/dist/agent/production-agent.js +1 -22
  143. package/dist/agent/run-manager.d.ts +7 -0
  144. package/dist/agent/run-manager.js +46 -3
  145. package/dist/agent/tool-approval-migrations.d.ts +40 -0
  146. package/dist/agent/tool-approval-migrations.js +51 -0
  147. package/dist/agent/tool-approval-store.js +5 -20
  148. package/dist/cli/templates-meta.js +2 -2
  149. package/dist/client/AgentPanel.js +10 -1
  150. package/dist/client/AssistantChat.d.ts +3 -0
  151. package/dist/client/AssistantChat.js +148 -140
  152. package/dist/client/agent-page/AgentTabsPage.js +2 -2
  153. package/dist/client/chat/attachment-adapters.js +8 -2
  154. package/dist/client/chat/tool-call-display.d.ts +1 -0
  155. package/dist/client/chat/tool-call-display.js +3 -1
  156. package/dist/client/chat-first.js +11 -5
  157. package/dist/client/hooks/index.d.ts +1 -1
  158. package/dist/client/hooks/index.js +1 -1
  159. package/dist/client/org/OrgSwitcher.d.ts +3 -1
  160. package/dist/client/org/OrgSwitcher.js +22 -16
  161. package/dist/client/org/WorkspaceNotice.js +3 -1
  162. package/dist/client/resources/index.d.ts +1 -1
  163. package/dist/client/resources/index.js +1 -1
  164. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  165. package/dist/client/resources/use-mcp-servers.js +14 -0
  166. package/dist/client/settings/AccountSettingsCard.js +8 -4
  167. package/dist/client/settings/SettingsPanel.js +46 -13
  168. package/dist/client/settings/SettingsTabsPage.js +8 -3
  169. package/dist/client/sharing/ShareButton.d.ts +7 -1
  170. package/dist/client/sharing/ShareButton.js +14 -7
  171. package/dist/client/sharing/ShareDialog.js +5 -1
  172. package/dist/client/sharing/useShareButtonController.d.ts +4 -0
  173. package/dist/client/sharing/useShareButtonController.js +15 -0
  174. package/dist/client/sharing/useShareDialogController.d.ts +7 -0
  175. package/dist/client/sharing/useShareDialogController.js +14 -0
  176. package/dist/client/sse-event-processor.js +37 -0
  177. package/dist/client/use-session.d.ts +2 -0
  178. package/dist/client/use-session.js +69 -5
  179. package/dist/collab/struct-routes.d.ts +1 -1
  180. package/dist/deploy/build.js +1 -1
  181. package/dist/file-upload/pre-upload-attachments.d.ts +3 -4
  182. package/dist/file-upload/pre-upload-attachments.js +65 -17
  183. package/dist/ingestion/index.d.ts +1 -0
  184. package/dist/ingestion/index.js +1 -0
  185. package/dist/ingestion/office.d.ts +3 -3
  186. package/dist/ingestion/office.js +24 -2
  187. package/dist/ingestion/spreadsheet.d.ts +41 -0
  188. package/dist/ingestion/spreadsheet.js +172 -0
  189. package/dist/integrations/adapters/slack.js +7 -3
  190. package/dist/localization/default-messages.d.ts +3 -0
  191. package/dist/localization/default-messages.js +3 -0
  192. package/dist/mcp/embed-app.js +20 -9
  193. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  194. package/dist/notifications/routes.d.ts +6 -6
  195. package/dist/oauth-tokens/migrations.d.ts +8 -0
  196. package/dist/oauth-tokens/migrations.js +36 -0
  197. package/dist/org/workspace-url.d.ts +8 -0
  198. package/dist/org/workspace-url.js +26 -2
  199. package/dist/secrets/routes.d.ts +3 -3
  200. package/dist/server/agent-chat-plugin.js +16 -8
  201. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  202. package/dist/server/auth.d.ts +0 -11
  203. package/dist/server/auth.js +37 -20
  204. package/dist/server/better-auth-migrations.js +23 -0
  205. package/dist/server/email-template.d.ts +5 -0
  206. package/dist/server/email-template.js +9 -0
  207. package/dist/server/embed-route.js +17 -5
  208. package/dist/server/identity-sso-store.d.ts +40 -66
  209. package/dist/server/identity-sso-store.js +187 -150
  210. package/dist/server/identity-sso.d.ts +17 -73
  211. package/dist/server/identity-sso.js +294 -297
  212. package/dist/server/onboarding-html.d.ts +0 -12
  213. package/dist/server/onboarding-html.js +46 -269
  214. package/dist/server/release-migrations.js +8 -0
  215. package/dist/shared/index.d.ts +1 -1
  216. package/dist/shared/index.js +1 -1
  217. package/dist/shared/workspace-app-id.d.ts +2 -0
  218. package/dist/shared/workspace-app-id.js +10 -0
  219. package/dist/sharing/actions/share-resource.d.ts +1 -0
  220. package/dist/sharing/actions/share-resource.js +20 -5
  221. package/dist/templates/chat/.agents/skills/sharing/SKILL.md +1 -1
  222. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +24 -0
  223. package/dist/templates/chat/app/root.tsx +0 -1
  224. package/dist/templates/chat/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  225. package/dist/templates/chat/public/manifest.json +0 -1
  226. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +24 -0
  227. package/dist/templates/default/app/root.tsx +0 -1
  228. package/dist/templates/default/public/manifest.json +0 -1
  229. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +24 -0
  230. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +22 -11
  231. package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +1 -1
  232. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +24 -0
  233. package/docs/content/cross-app-sso.mdx +85 -27
  234. package/docs/content/template-mail.mdx +0 -6
  235. package/package.json +4 -3
  236. package/src/templates/chat/.agents/skills/sharing/SKILL.md +1 -1
  237. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +24 -0
  238. package/src/templates/chat/app/root.tsx +0 -1
  239. package/src/templates/chat/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  240. package/src/templates/chat/public/manifest.json +0 -1
  241. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +24 -0
  242. package/src/templates/default/app/root.tsx +0 -1
  243. package/src/templates/default/public/manifest.json +0 -1
  244. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +24 -0
  245. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +22 -11
  246. package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +1 -1
  247. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +24 -0
  248. package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +0 -6
  249. package/corpus/templates/clips/app/components/meetings/google-oauth-identity-notice.tsx +0 -51
  250. package/corpus/templates/clips/changelog/2026-07-30-google-calendar-warning.md +0 -6
@@ -22,6 +22,7 @@ export const ChatRunningContext = React.createContext(false);
22
22
  export const ChatRunningRunIdContext = React.createContext(null);
23
23
  export const ChatRunningTurnIdContext = React.createContext(null);
24
24
  export const ChatRunDurationContext = React.createContext(null);
25
+ export const SuppressInlineOpenAppContext = React.createContext(false);
25
26
  export const ASSISTANT_VISIBLE_TOOL_CALL_LIMIT = 3;
26
27
  /**
27
28
  * Keeps the tool-call stack layout-transparent. Tool-entry motion is disabled
@@ -291,6 +292,7 @@ export function ToolCallDisplay({ toolName, toolCallId, argsText, args, result,
291
292
  return wrapToolDisplay(_jsx(ToolCallDisplayGeneric, { toolName: toolName, toolCallId: toolCallId, argsText: argsText, args: args, result: result, mcpApp: mcpApp, chatUI: chatUI, isRunning: isRunning, outcome: outcome, isActiveTail: showActiveTail, structuredMeta: structuredMeta, approval: approval, repeatCount: repeatCount }));
292
293
  }
293
294
  function ToolCallDisplayGeneric({ toolName, toolCallId, argsText, args, result, mcpApp, chatUI, isRunning, outcome, isActiveTail, structuredMeta, approval, repeatCount, }) {
295
+ const suppressInlineOpenApp = React.useContext(SuppressInlineOpenAppContext);
294
296
  const isRawCallAgent = toolName === "call-agent";
295
297
  const isAgentCall = toolName.startsWith("agent:") || isRawCallAgent;
296
298
  const [expanded, setExpanded] = useState(isAgentCall);
@@ -405,7 +407,7 @@ function ToolCallDisplayGeneric({ toolName, toolCallId, argsText, args, result,
405
407
  ? structuredMeta.agentDurationMs
406
408
  : agentActivity?.durationMs }));
407
409
  }
408
- return (_jsxs("div", { className: "group/tool my-0.5 w-full overflow-hidden", children: [mcpApp && _jsx(McpAppRenderer, { app: mcpApp, className: "mb-1.5" }), _jsxs("button", { type: "button", onClick: () => canExpand && setExpanded(!isExpanded), "aria-expanded": canExpand ? isExpanded : undefined, className: cn("flex w-full items-center gap-1.5 rounded-md py-0.5 text-left text-[13px] text-muted-foreground transition-colors", canExpand && "hover:text-foreground", isRunning && "text-muted-foreground"), children: [_jsx("span", { className: "relative flex size-4 shrink-0 items-center justify-center", children: isRunning ? (_jsx(IconLoader2, { className: "size-3.5 animate-spin" })) : isAgentError ? (_jsx(IconCircleX, { className: "size-3.5 text-destructive" })) : isUnknownOutcome ? (_jsx(IconAlertTriangle, { className: "size-3.5 text-muted-foreground" })) : (_jsxs(_Fragment, { children: [_jsx(ToolIcon, { className: cn("size-3.5 transition-opacity", canExpand && "group-hover/tool:opacity-0") }), canExpand && (_jsx(IconChevronRight, { className: cn("absolute size-3.5 opacity-0 transition-[opacity,transform] group-hover/tool:opacity-100", isExpanded && "rotate-90") }))] })) }), _jsx("span", { className: cn("min-w-0 truncate font-normal", isActiveTail && "agent-running-shimmer"), children: displayName }), repeatCount && repeatCount > 1 && (_jsxs("span", { className: "shrink-0 rounded border border-border/60 px-1.5 py-0.5 text-[10px] leading-none text-muted-foreground", title: `Repeated ${repeatCount} times`, children: [repeatCount, "x"] }))] }), _jsx(AnimatedCollapse, { open: isExpanded && !isAgentCall && (hasArgs || result !== undefined), children: _jsxs("div", { className: "mt-1 space-y-2 pl-5", children: [inputPayload && (_jsx(SimpleCodeViewer, { text: inputPayload.text, lang: inputPayload.lang })), resultPayload && (_jsx(ToolOutputPopover, { open: outputOpen, onOpenChange: setOutputOpen, title: outputTitle, payload: resultPayload, children: _jsx("button", { type: "button", "aria-label": `View ${toolName} output`, className: "inline-flex size-6 items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-accent hover:text-foreground", children: _jsx(IconCode, { className: "size-3.5" }) }) }))] }) }), isUnknownOutcome && (_jsx("p", { role: "status", className: "ps-5 text-xs text-muted-foreground", children: "Interrupted before this finished reporting \u2014 it may or may not have completed. Check before retrying." })), approval && (_jsx(ApprovalAffordance, { toolName: toolName, toolCallId: toolCallId, approval: approval }))] }));
410
+ return (_jsxs("div", { className: "group/tool my-0.5 w-full overflow-hidden", children: [mcpApp && !(suppressInlineOpenApp && toolName === "open_app") && (_jsx(McpAppRenderer, { app: mcpApp, className: "mb-1.5" })), _jsxs("button", { type: "button", onClick: () => canExpand && setExpanded(!isExpanded), "aria-expanded": canExpand ? isExpanded : undefined, className: cn("flex w-full items-center gap-1.5 rounded-md py-0.5 text-left text-[13px] text-muted-foreground transition-colors", canExpand && "hover:text-foreground", isRunning && "text-muted-foreground"), children: [_jsx("span", { className: "relative flex size-4 shrink-0 items-center justify-center", children: isRunning ? (_jsx(IconLoader2, { className: "size-3.5 animate-spin" })) : isAgentError ? (_jsx(IconCircleX, { className: "size-3.5 text-destructive" })) : isUnknownOutcome ? (_jsx(IconAlertTriangle, { className: "size-3.5 text-muted-foreground" })) : (_jsxs(_Fragment, { children: [_jsx(ToolIcon, { className: cn("size-3.5 transition-opacity", canExpand && "group-hover/tool:opacity-0") }), canExpand && (_jsx(IconChevronRight, { className: cn("absolute size-3.5 opacity-0 transition-[opacity,transform] group-hover/tool:opacity-100", isExpanded && "rotate-90") }))] })) }), _jsx("span", { className: cn("min-w-0 truncate font-normal", isActiveTail && "agent-running-shimmer"), children: displayName }), repeatCount && repeatCount > 1 && (_jsxs("span", { className: "shrink-0 rounded border border-border/60 px-1.5 py-0.5 text-[10px] leading-none text-muted-foreground", title: `Repeated ${repeatCount} times`, children: [repeatCount, "x"] }))] }), _jsx(AnimatedCollapse, { open: isExpanded && !isAgentCall && (hasArgs || result !== undefined), children: _jsxs("div", { className: "mt-1 space-y-2 pl-5", children: [inputPayload && (_jsx(SimpleCodeViewer, { text: inputPayload.text, lang: inputPayload.lang })), resultPayload && (_jsx(ToolOutputPopover, { open: outputOpen, onOpenChange: setOutputOpen, title: outputTitle, payload: resultPayload, children: _jsx("button", { type: "button", "aria-label": `View ${toolName} output`, className: "inline-flex size-6 items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-accent hover:text-foreground", children: _jsx(IconCode, { className: "size-3.5" }) }) }))] }) }), isUnknownOutcome && (_jsx("p", { role: "status", className: "ps-5 text-xs text-muted-foreground", children: "Interrupted before this finished reporting \u2014 it may or may not have completed. Check before retrying." })), approval && (_jsx(ApprovalAffordance, { toolName: toolName, toolCallId: toolCallId, approval: approval }))] }));
409
411
  }
410
412
  function AgentCallCell({ agentName, activity, progress, responseText, isRunning, isError, durationMs, }) {
411
413
  const t = useT();
@@ -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)),
@@ -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
@@ -50,6 +50,15 @@ const ROLE_OPTIONS = [
50
50
  description: "Can edit and manage access",
51
51
  },
52
52
  ];
53
+ function roleMeta(role, copy) {
54
+ const base = ROLE_OPTIONS.find((option) => option.value === role) ?? ROLE_OPTIONS[0];
55
+ const override = copy?.[role];
56
+ return {
57
+ ...base,
58
+ label: override?.label ?? base.label,
59
+ description: override?.description ?? base.description,
60
+ };
61
+ }
53
62
  function isShareNestedOverlayTarget(target) {
54
63
  return (target instanceof Element &&
55
64
  target.closest(`[${SHARE_NESTED_OVERLAY_ATTR}]`) !== null);
@@ -88,7 +97,7 @@ export function ShareButton(props) {
88
97
  }
89
98
  function SharePanel(props) {
90
99
  const { resourceTitle, controller } = props;
91
- const { inviteEmail, setInviteEmail: onInviteEmailChange, activeShareTab, handleShareTabChange, data, policy, visibility, canManage, role, setRole, notifyPeople, setNotifyPeople, shareError, setShareError, suggestionsOpen, setSuggestionsOpen, inFlight, memberSearch, memberSuggestions, knownMembers, shares, handleVisibility, handleHideInSearch, handleAdd, handleChangeRole, handleRemove, } = controller;
100
+ const { inviteEmail, setInviteEmail: onInviteEmailChange, activeShareTab, handleShareTabChange, data, policy, visibility, canManage, role, setRole, notifyPeople, setNotifyPeople, shareMessage, setShareMessage, messageOpen, setMessageOpen, shareError, setShareError, suggestionsOpen, setSuggestionsOpen, inFlight, memberSearch, memberSuggestions, knownMembers, shares, handleVisibility, handleHideInSearch, handleAdd, handleChangeRole, handleRemove, } = controller;
92
101
  const hasInviteEmail = inviteEmail.trim().length > 0;
93
102
  const isLoading = data === undefined;
94
103
  const meta = visibilityMeta(visibility, props.visibilityCopy);
@@ -117,7 +126,7 @@ function SharePanel(props) {
117
126
  setShareError(null);
118
127
  }, onSubmit: handleAdd, placeholder: policy.requireOrgMemberForUserShares
119
128
  ? "Add people from your organization"
120
- : "Add people by email", suggestions: memberSuggestions, search: memberSearch }), _jsx(RoleSelect, { value: role, onChange: setRole }), _jsx("button", { type: "button", onClick: handleAdd, disabled: !hasInviteEmail, className: BUTTON_PRIMARY_SM, children: "Add" })] }), shareError ? (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: shareError })) : null, hasInviteEmail ? (_jsxs("label", { className: "inline-flex items-center gap-2 text-xs text-muted-foreground", children: [_jsx("input", { type: "checkbox", checked: notifyPeople, onChange: (e) => setNotifyPeople(e.target.checked), className: "h-4 w-4 rounded border-input accent-primary" }), "Notify people"] })) : null] })) : null, _jsx("div", { className: "mb-2 text-sm font-semibold", children: peopleAccessLabel }), _jsxs("ul", { className: "mb-4 flex flex-col gap-1 list-none p-0 m-0", children: [data?.ownerEmail ? (_jsxs("li", { className: "flex items-center gap-3 px-1 py-1.5 text-sm", children: [_jsx(Avatar, { label: displayName(data.ownerEmail, knownMembers) }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: displayName(data.ownerEmail, knownMembers) }), _jsx("span", { className: "text-xs text-muted-foreground", children: "Owner" })] })) : null, shares.map((s) => (_jsxs("li", { className: cn("flex items-center gap-3 px-1 py-1.5 text-sm", inFlight.has(keyOf(s)) && "opacity-60"), children: [_jsx(Avatar, { label: principalLabel(s, knownMembers), org: s.principalType === "org" }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: principalLabel(s, knownMembers) }), canManage ? (_jsx(RoleSelect, { value: s.role, onChange: (r) => handleChangeRole(s, r), disabled: inFlight.has(keyOf(s)), plain: true })) : (_jsx("span", { className: "text-xs text-muted-foreground", children: cap(s.role) })), canManage ? (_jsx("button", { type: "button", "aria-label": "Remove", onClick: () => handleRemove(s), disabled: inFlight.has(keyOf(s)), className: BUTTON_GHOST_ICON, children: _jsx(IconTrash, { size: 14 }) })) : null] }, keyOf(s)))), !shares.length && !data?.ownerEmail ? (_jsx("li", { className: "px-1 py-1.5 text-sm text-muted-foreground", children: "No one has access yet." })) : null] }), _jsx("div", { className: "mb-2 text-sm font-semibold", children: generalAccessLabel }), _jsxs("div", { className: "mb-4 flex items-center gap-3", children: [_jsx("span", { "aria-hidden": true, className: "inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground", children: _jsx(meta.Icon, { size: 16, strokeWidth: 1.75 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(VisibilitySelect, { value: visibility, onChange: handleVisibility, disabled: !canManage, visibilityCopy: props.visibilityCopy, allowPublic: policy.allowPublic }), _jsxs("div", { className: "mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground", children: [_jsx("span", { children: meta.description }), visibility === "org" && props.hideInSearchControl ? (_jsx(AdvancedAccessPopover, { control: props.hideInSearchControl, canManage: canManage, onToggle: handleHideInSearch })) : null] })] })] }), shareError && !canManage ? (_jsx("div", { role: "alert", className: "mb-4 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: shareError })) : null, props.accessNote ? (_jsx("div", { className: "mb-4 rounded-md border border-border bg-muted/35 p-3 text-xs text-muted-foreground", children: props.accessNote })) : null, showShareLinks && shareUrlPlacement === "bottom" ? shareLinks : null, props.shareFooterContent] }));
129
+ : "Add people by email", suggestions: memberSuggestions, search: memberSearch }), _jsx(RoleSelect, { value: role, onChange: setRole, roleCopy: props.roleCopy }), _jsx("button", { type: "button", onClick: handleAdd, disabled: !hasInviteEmail, className: BUTTON_PRIMARY_SM, children: "Add" })] }), shareError ? (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: shareError })) : null, hasInviteEmail ? (_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 text-xs text-muted-foreground", children: [_jsxs("label", { className: "inline-flex items-center gap-2", children: [_jsx("input", { type: "checkbox", checked: notifyPeople, onChange: (e) => setNotifyPeople(e.target.checked), className: "h-4 w-4 rounded border-input accent-primary" }), "Notify people"] }), notifyPeople ? (_jsx("button", { type: "button", "aria-expanded": messageOpen, onClick: () => setMessageOpen(!messageOpen), className: "rounded-sm px-1 py-0.5 font-medium text-foreground underline decoration-border underline-offset-2 transition-colors hover:bg-accent hover:text-accent-foreground", children: messageOpen ? "Hide message" : "Add a message" })) : null] })) : null, hasInviteEmail && notifyPeople && messageOpen ? (_jsx("div", { className: "rounded-md border border-border/70 bg-muted/20 p-2.5", children: _jsx("textarea", { "aria-label": "Message", placeholder: "Add a short note (optional)", value: shareMessage, onChange: (event) => setShareMessage(event.target.value), maxLength: 500, rows: 3, className: "w-full resize-y rounded-md border border-input bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background" }) })) : null] })) : null, _jsx("div", { className: "mb-2 text-sm font-semibold", children: peopleAccessLabel }), _jsxs("ul", { className: "mb-4 flex flex-col gap-1 list-none p-0 m-0", children: [data?.ownerEmail ? (_jsxs("li", { className: "flex items-center gap-3 px-1 py-1.5 text-sm", children: [_jsx(Avatar, { label: displayName(data.ownerEmail, knownMembers) }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: displayName(data.ownerEmail, knownMembers) }), _jsx("span", { className: "text-xs text-muted-foreground", children: "Owner" })] })) : null, shares.map((s) => (_jsxs("li", { className: cn("flex items-center gap-3 px-1 py-1.5 text-sm", inFlight.has(keyOf(s)) && "opacity-60"), children: [_jsx(Avatar, { label: principalLabel(s, knownMembers), org: s.principalType === "org" }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: principalLabel(s, knownMembers) }), canManage ? (_jsx(RoleSelect, { value: s.role, onChange: (r) => handleChangeRole(s, r), disabled: inFlight.has(keyOf(s)), plain: true, roleCopy: props.roleCopy })) : (_jsx("span", { className: "text-xs text-muted-foreground", children: roleMeta(s.role, props.roleCopy).label })), canManage ? (_jsx("button", { type: "button", "aria-label": "Remove", onClick: () => handleRemove(s), disabled: inFlight.has(keyOf(s)), className: BUTTON_GHOST_ICON, children: _jsx(IconTrash, { size: 14 }) })) : null] }, keyOf(s)))), !shares.length && !data?.ownerEmail ? (_jsx("li", { className: "px-1 py-1.5 text-sm text-muted-foreground", children: "No one has access yet." })) : null] }), _jsx("div", { className: "mb-2 text-sm font-semibold", children: generalAccessLabel }), _jsxs("div", { className: "mb-4 flex items-center gap-3", children: [_jsx("span", { "aria-hidden": true, className: "inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground", children: _jsx(meta.Icon, { size: 16, strokeWidth: 1.75 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(VisibilitySelect, { value: visibility, onChange: handleVisibility, disabled: !canManage, visibilityCopy: props.visibilityCopy, allowPublic: policy.allowPublic }), _jsxs("div", { className: "mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground", children: [_jsx("span", { children: meta.description }), visibility === "org" && props.hideInSearchControl ? (_jsx(AdvancedAccessPopover, { control: props.hideInSearchControl, canManage: canManage, onToggle: handleHideInSearch })) : null] })] })] }), shareError && !canManage ? (_jsx("div", { role: "alert", className: "mb-4 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: shareError })) : null, props.accessNote ? (_jsx("div", { className: "mb-4 rounded-md border border-border bg-muted/35 p-3 text-xs text-muted-foreground", children: props.accessNote })) : null, showShareLinks && shareUrlPlacement === "bottom" ? shareLinks : null, props.shareFooterContent] }));
121
130
  if (!hasTabs)
122
131
  return sharePanel;
123
132
  const tabs = [
@@ -271,10 +280,11 @@ function SelectItems({ items }) {
271
280
  return (_jsx(_Fragment, { children: items.map((it) => (_jsxs(Select.Item, { value: it.value, className: selectItemClass, children: [_jsx("span", { className: "absolute start-2 top-2 flex h-4 w-4 items-center justify-center", children: _jsx(Select.ItemIndicator, { children: _jsx(IconCheck, { size: 14 }) }) }), _jsxs("span", { className: "flex flex-col", children: [_jsx(Select.ItemText, { children: it.label }), it.description ? (_jsx("span", { className: "text-xs text-muted-foreground", children: it.description })) : null] })] }, it.value))) }));
272
281
  }
273
282
  function RoleSelect(props) {
274
- const current = ROLE_OPTIONS.find((o) => o.value === props.value) ?? ROLE_OPTIONS[0];
283
+ const current = roleMeta(props.value, props.roleCopy);
284
+ const options = ROLE_OPTIONS.map((option) => roleMeta(option.value, props.roleCopy));
275
285
  return (_jsxs(Select.Root, { value: props.value, onValueChange: (v) => props.onChange(v), disabled: props.disabled, children: [_jsxs(Select.Trigger, { className: props.plain
276
286
  ? cn(BUTTON_BASE, "h-7 px-2 bg-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground")
277
- : cn(BUTTON_BASE, "h-9 px-3 border border-input bg-card hover:bg-accent hover:text-accent-foreground"), "aria-label": "Role", children: [_jsx(Select.Value, { children: current.label }), _jsx(Select.Icon, { children: _jsx(IconChevronDown, { size: 14 }) })] }), _jsx(Select.Portal, { children: _jsx(Select.Content, { "data-agent-native-share-overlay": "", className: selectContentClass, position: "popper", sideOffset: 4, children: _jsx(Select.Viewport, { children: _jsx(SelectItems, { items: ROLE_OPTIONS }) }) }) })] }));
287
+ : cn(BUTTON_BASE, "h-9 px-3 border border-input bg-card hover:bg-accent hover:text-accent-foreground"), "aria-label": "Role", children: [_jsx(Select.Value, { children: current.label }), _jsx(Select.Icon, { children: _jsx(IconChevronDown, { size: 14 }) })] }), _jsx(Select.Portal, { children: _jsx(Select.Content, { "data-agent-native-share-overlay": "", className: selectContentClass, position: "popper", sideOffset: 4, children: _jsx(Select.Viewport, { children: _jsx(SelectItems, { items: options }) }) }) })] }));
278
288
  }
279
289
  function VisibilitySelect(props) {
280
290
  const allowPrivate = props.allowPrivate !== false;
@@ -302,9 +312,6 @@ function Avatar({ label, org }) {
302
312
  function keyOf(s) {
303
313
  return `${s.principalType}:${s.principalId}`;
304
314
  }
305
- function cap(s) {
306
- return s.charAt(0).toUpperCase() + s.slice(1);
307
- }
308
315
  function initials(s) {
309
316
  const name = s.split("@")[0] ?? s;
310
317
  return (name[0] ?? "?").toUpperCase();