@agent-native/core 0.151.1 → 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 (256) 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/slide-editing/SKILL.md +13 -0
  131. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +24 -0
  132. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -0
  133. package/corpus/templates/slides/app/i18n/en-US.ts +2 -0
  134. package/corpus/templates/slides/app/root.tsx +0 -1
  135. package/corpus/templates/slides/changelog/2026-08-10-asking-the-agent-for-several-slide-animations-at-once-now-ke.md +6 -0
  136. package/corpus/templates/slides/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  137. package/corpus/templates/slides/changelog/2026-08-11-share-dialog-uses-commenter-terminology.md +6 -0
  138. package/corpus/templates/slides/public/manifest.json +0 -1
  139. package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +1 -1
  140. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +24 -0
  141. package/corpus/templates/tasks/app/root.tsx +0 -1
  142. package/corpus/templates/tasks/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  143. package/corpus/templates/tasks/public/manifest.json +0 -1
  144. package/dist/agent/production-agent.js +1 -22
  145. package/dist/agent/run-manager.d.ts +7 -0
  146. package/dist/agent/run-manager.js +46 -3
  147. package/dist/agent/tool-approval-migrations.d.ts +40 -0
  148. package/dist/agent/tool-approval-migrations.js +51 -0
  149. package/dist/agent/tool-approval-store.js +5 -20
  150. package/dist/cli/templates-meta.js +2 -2
  151. package/dist/client/AgentPanel.js +10 -1
  152. package/dist/client/AssistantChat.d.ts +3 -0
  153. package/dist/client/AssistantChat.js +148 -140
  154. package/dist/client/agent-page/AgentTabsPage.js +2 -2
  155. package/dist/client/chat/attachment-adapters.js +8 -2
  156. package/dist/client/chat/tool-call-display.d.ts +1 -0
  157. package/dist/client/chat/tool-call-display.js +3 -1
  158. package/dist/client/chat-first.js +11 -5
  159. package/dist/client/hooks/index.d.ts +1 -1
  160. package/dist/client/hooks/index.js +1 -1
  161. package/dist/client/org/OrgSwitcher.d.ts +3 -1
  162. package/dist/client/org/OrgSwitcher.js +22 -16
  163. package/dist/client/org/WorkspaceNotice.js +3 -1
  164. package/dist/client/resources/index.d.ts +1 -1
  165. package/dist/client/resources/index.js +1 -1
  166. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  167. package/dist/client/resources/use-mcp-servers.js +14 -0
  168. package/dist/client/settings/AccountSettingsCard.js +8 -4
  169. package/dist/client/settings/SettingsPanel.js +46 -13
  170. package/dist/client/settings/SettingsTabsPage.js +8 -3
  171. package/dist/client/sharing/ShareButton.d.ts +7 -1
  172. package/dist/client/sharing/ShareButton.js +14 -7
  173. package/dist/client/sharing/ShareDialog.js +5 -1
  174. package/dist/client/sharing/useShareButtonController.d.ts +4 -0
  175. package/dist/client/sharing/useShareButtonController.js +15 -0
  176. package/dist/client/sharing/useShareDialogController.d.ts +7 -0
  177. package/dist/client/sharing/useShareDialogController.js +14 -0
  178. package/dist/client/sse-event-processor.js +37 -0
  179. package/dist/client/use-session.d.ts +2 -0
  180. package/dist/client/use-session.js +69 -5
  181. package/dist/collab/struct-routes.d.ts +1 -1
  182. package/dist/deploy/build.js +1 -1
  183. package/dist/file-upload/pre-upload-attachments.d.ts +3 -4
  184. package/dist/file-upload/pre-upload-attachments.js +65 -17
  185. package/dist/ingestion/index.d.ts +1 -0
  186. package/dist/ingestion/index.js +1 -0
  187. package/dist/ingestion/office.d.ts +3 -3
  188. package/dist/ingestion/office.js +24 -2
  189. package/dist/ingestion/spreadsheet.d.ts +41 -0
  190. package/dist/ingestion/spreadsheet.js +172 -0
  191. package/dist/integrations/adapters/slack.js +7 -3
  192. package/dist/localization/default-messages.d.ts +3 -0
  193. package/dist/localization/default-messages.js +3 -0
  194. package/dist/mcp/embed-app.js +20 -9
  195. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  196. package/dist/notifications/routes.d.ts +6 -6
  197. package/dist/oauth-tokens/migrations.d.ts +8 -0
  198. package/dist/oauth-tokens/migrations.js +36 -0
  199. package/dist/observability/routes.d.ts +5 -5
  200. package/dist/org/workspace-url.d.ts +8 -0
  201. package/dist/org/workspace-url.js +26 -2
  202. package/dist/progress/routes.d.ts +1 -1
  203. package/dist/secrets/routes.d.ts +3 -3
  204. package/dist/server/agent-chat-plugin.js +16 -8
  205. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  206. package/dist/server/auth.d.ts +0 -11
  207. package/dist/server/auth.js +37 -20
  208. package/dist/server/better-auth-migrations.js +23 -0
  209. package/dist/server/email-template.d.ts +5 -0
  210. package/dist/server/email-template.js +9 -0
  211. package/dist/server/embed-route.js +17 -5
  212. package/dist/server/identity-sso-store.d.ts +40 -66
  213. package/dist/server/identity-sso-store.js +187 -150
  214. package/dist/server/identity-sso.d.ts +17 -73
  215. package/dist/server/identity-sso.js +294 -297
  216. package/dist/server/onboarding-html.d.ts +0 -12
  217. package/dist/server/onboarding-html.js +46 -269
  218. package/dist/server/realtime-token.d.ts +1 -1
  219. package/dist/server/release-migrations.js +8 -0
  220. package/dist/server/transcribe-voice.d.ts +1 -1
  221. package/dist/shared/index.d.ts +1 -1
  222. package/dist/shared/index.js +1 -1
  223. package/dist/shared/workspace-app-id.d.ts +2 -0
  224. package/dist/shared/workspace-app-id.js +10 -0
  225. package/dist/sharing/actions/share-resource.d.ts +1 -0
  226. package/dist/sharing/actions/share-resource.js +20 -5
  227. package/dist/templates/chat/.agents/skills/sharing/SKILL.md +1 -1
  228. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +24 -0
  229. package/dist/templates/chat/app/root.tsx +0 -1
  230. package/dist/templates/chat/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  231. package/dist/templates/chat/public/manifest.json +0 -1
  232. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +24 -0
  233. package/dist/templates/default/app/root.tsx +0 -1
  234. package/dist/templates/default/public/manifest.json +0 -1
  235. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +24 -0
  236. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +22 -11
  237. package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +1 -1
  238. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +24 -0
  239. package/docs/content/cross-app-sso.mdx +85 -27
  240. package/docs/content/template-mail.mdx +0 -6
  241. package/package.json +4 -3
  242. package/src/templates/chat/.agents/skills/sharing/SKILL.md +1 -1
  243. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +24 -0
  244. package/src/templates/chat/app/root.tsx +0 -1
  245. package/src/templates/chat/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  246. package/src/templates/chat/public/manifest.json +0 -1
  247. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +24 -0
  248. package/src/templates/default/app/root.tsx +0 -1
  249. package/src/templates/default/public/manifest.json +0 -1
  250. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +24 -0
  251. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +22 -11
  252. package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +1 -1
  253. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +24 -0
  254. package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +0 -6
  255. package/corpus/templates/clips/app/components/meetings/google-oauth-identity-notice.tsx +0 -51
  256. package/corpus/templates/clips/changelog/2026-07-30-google-calendar-warning.md +0 -6
@@ -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();
@@ -56,7 +56,11 @@ function InviteTab({ controller, showVisibility, }) {
56
56
  return (_jsxs("div", { className: "space-y-4", children: [controller.canManage ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-stretch gap-2", children: [_jsx("input", { type: "email", placeholder: controller.labels.addPeopleByEmail, value: controller.invite.email, onChange: (event) => controller.invite.setEmail(event.currentTarget.value), onKeyDown: (event) => {
57
57
  if (event.key === "Enter")
58
58
  controller.invite.submit();
59
- }, autoComplete: "off", className: "flex-1 min-w-0 h-9 rounded-md border border-input bg-background px-3 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" }), _jsx(RoleSelect, { controller: controller })] }), controller.invite.showNotifyPeople ? (_jsxs("label", { className: "inline-flex items-center gap-2 text-xs text-muted-foreground", children: [_jsx("input", { type: "checkbox", checked: controller.invite.notifyPeople, onChange: (event) => controller.invite.setNotifyPeople(event.currentTarget.checked), className: "h-4 w-4 rounded border-input accent-primary" }), controller.labels.notifyPeople] })) : null] })) : null, _jsxs("div", { children: [_jsx("div", { className: "mb-2 text-sm font-semibold", children: controller.labels.peopleWithAccess }), _jsxs("ul", { className: "flex flex-col gap-1 list-none p-0 m-0", children: [controller.people.map((person) => (_jsx(PersonRow, { person: person, canManage: controller.canManage, removeLabel: controller.labels.remove, onRemove: controller.removeShare }, person.key))), !controller.people.length ? (_jsx("li", { className: "px-1 py-1.5 text-sm text-muted-foreground", children: controller.labels.noAccess })) : null] })] }), showVisibility ? (_jsxs("div", { children: [_jsx("div", { className: "mb-2 text-sm font-semibold", children: controller.labels.generalAccess }), _jsxs("div", { className: "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(Icon, { size: 16, strokeWidth: 1.75 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(VisibilitySelect, { controller: controller }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground", children: controller.visibility.description })] })] })] })) : null] }));
59
+ }, autoComplete: "off", className: "flex-1 min-w-0 h-9 rounded-md border border-input bg-background px-3 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" }), _jsx(RoleSelect, { controller: controller })] }), controller.invite.showNotifyPeople ? (_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: controller.invite.notifyPeople, onChange: (event) => controller.invite.setNotifyPeople(event.currentTarget.checked), className: "h-4 w-4 rounded border-input accent-primary" }), controller.labels.notifyPeople] }), controller.invite.notifyPeople ? (_jsx(ActionButton, { type: "button", emphasis: "ghost", "aria-expanded": controller.invite.messageOpen, onPress: () => controller.invite.setMessageOpen(!controller.invite.messageOpen), className: "!h-auto !rounded-sm !px-1 !py-0.5 text-xs font-medium !text-foreground underline decoration-border underline-offset-2 hover:!bg-accent hover:!text-accent-foreground active:!scale-100", children: controller.invite.messageOpen
60
+ ? controller.labels.hideMessage
61
+ : controller.labels.addMessage })) : null] })) : null, controller.invite.showNotifyPeople &&
62
+ controller.invite.notifyPeople &&
63
+ controller.invite.messageOpen ? (_jsx(TextArea, { "aria-label": controller.labels.addMessage, placeholder: controller.labels.messagePlaceholder, value: controller.invite.message, onChange: (value) => controller.invite.setMessage(value), maxLength: 500, rows: 3, className: "min-h-20 resize-y text-sm" })) : null] })) : null, _jsxs("div", { children: [_jsx("div", { className: "mb-2 text-sm font-semibold", children: controller.labels.peopleWithAccess }), _jsxs("ul", { className: "flex flex-col gap-1 list-none p-0 m-0", children: [controller.people.map((person) => (_jsx(PersonRow, { person: person, canManage: controller.canManage, removeLabel: controller.labels.remove, onRemove: controller.removeShare }, person.key))), !controller.people.length ? (_jsx("li", { className: "px-1 py-1.5 text-sm text-muted-foreground", children: controller.labels.noAccess })) : null] })] }), showVisibility ? (_jsxs("div", { children: [_jsx("div", { className: "mb-2 text-sm font-semibold", children: controller.labels.generalAccess }), _jsxs("div", { className: "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(Icon, { size: 16, strokeWidth: 1.75 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(VisibilitySelect, { controller: controller }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground", children: controller.visibility.description })] })] })] })) : null] }));
60
64
  }
61
65
  function PersonRow({ person, canManage, removeLabel, onRemove, }) {
62
66
  return (_jsxs("li", { className: "flex items-center gap-3 px-1 py-1.5 text-sm", children: [_jsx(DesignSystemAvatar, { name: person.label, fallback: person.principalType === "org" ? (_jsx(IconUsersGroup, { size: 14, strokeWidth: 1.75 })) : (person.avatarText), size: "compact", className: "inline-flex h-7 w-7 shrink-0 text-[11px] font-semibold" }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: person.label }), _jsx(Status, { tone: "neutral", size: "compact", className: "border-0 bg-transparent px-0 text-xs text-muted-foreground", children: person.roleLabel }), canManage && person.share ? (_jsx(IconButton, { intent: "danger", emphasis: "ghost", size: "compact", icon: _jsx(IconTrash, { size: 14 }), label: removeLabel, "aria-label": removeLabel, onPress: () => onRemove(person.share), className: cn(BUTTON_GHOST_ICON, "[&_svg]:!size-auto") })) : null] }));
@@ -71,6 +71,10 @@ export interface ShareButtonController {
71
71
  setRole: (role: ShareButtonRole) => void;
72
72
  notifyPeople: boolean;
73
73
  setNotifyPeople: (notify: boolean) => void;
74
+ shareMessage: string;
75
+ setShareMessage: (message: string) => void;
76
+ messageOpen: boolean;
77
+ setMessageOpen: (open: boolean) => void;
74
78
  shareError: string | null;
75
79
  setShareError: (error: string | null) => void;
76
80
  suggestionsOpen: boolean;
@@ -25,6 +25,8 @@ export function useShareButtonController(options) {
25
25
  }, [options, shareTabDefaultValue, sharesQuery, visibilityOverride]);
26
26
  useEffect(() => {
27
27
  setInviteEmail("");
28
+ setShareMessage("");
29
+ setMessageOpen(false);
28
30
  }, [options.resourceId, options.resourceType]);
29
31
  useEffect(() => {
30
32
  if (!options.defaultOpen || appliedDefaultOpenRef.current)
@@ -102,6 +104,8 @@ export function useShareButtonController(options) {
102
104
  // the popover cannot drop an in-flight mutation or an unsent invite.
103
105
  const [role, setRole] = useState("viewer");
104
106
  const [notifyPeople, setNotifyPeople] = useState(true);
107
+ const [shareMessage, setShareMessage] = useState("");
108
+ const [messageOpen, setMessageOpen] = useState(false);
105
109
  const [suggestionsOpen, setSuggestionsOpen] = useState(false);
106
110
  const [pendingAdds, setPendingAdds] = useState([]);
107
111
  const [pendingRemoves, setPendingRemoves] = useState(new Set());
@@ -177,6 +181,7 @@ export function useShareButtonController(options) {
177
181
  const trimmed = inviteEmail.trim();
178
182
  if (!trimmed || !canManage)
179
183
  return;
184
+ const message = notifyPeople ? shareMessage.trim() : "";
180
185
  const optimistic = {
181
186
  id: `pending-${trimmed}`,
182
187
  principalType: "user",
@@ -189,6 +194,8 @@ export function useShareButtonController(options) {
189
194
  setShareError(null);
190
195
  setPendingAdds((previous) => [...previous, optimistic]);
191
196
  setInviteEmail("");
197
+ setShareMessage("");
198
+ setMessageOpen(false);
192
199
  setSuggestionsOpen(false);
193
200
  addInFlight(key);
194
201
  const previous = optimisticallyUpdateShareCache(queryClient, shareQueryKey, (cached) => cached ? { ...cached, shares: [...cached.shares, optimistic] } : cached);
@@ -200,6 +207,7 @@ export function useShareButtonController(options) {
200
207
  role,
201
208
  notify: notifyPeople,
202
209
  resourceUrl: getNotificationUrl(options.shareUrl),
210
+ ...(message ? { message } : {}),
203
211
  }, {
204
212
  onSuccess: () => {
205
213
  sharesQuery.refetch().then(() => {
@@ -212,6 +220,8 @@ export function useShareButtonController(options) {
212
220
  setPendingAdds((previous) => previous.filter((item) => item.id !== optimistic.id));
213
221
  clearInFlight(key);
214
222
  setInviteEmail(trimmed);
223
+ setShareMessage((current) => current || message);
224
+ setMessageOpen((current) => current || Boolean(message));
215
225
  setShareError(extractShareErrorMessage(error));
216
226
  },
217
227
  });
@@ -226,6 +236,7 @@ export function useShareButtonController(options) {
226
236
  options.resourceType,
227
237
  options.shareUrl,
228
238
  role,
239
+ shareMessage,
229
240
  share,
230
241
  queryClient,
231
242
  shareQueryKey,
@@ -361,6 +372,10 @@ export function useShareButtonController(options) {
361
372
  role,
362
373
  setRole,
363
374
  notifyPeople,
375
+ shareMessage,
376
+ setShareMessage,
377
+ messageOpen,
378
+ setMessageOpen,
364
379
  setNotifyPeople,
365
380
  shareError,
366
381
  setShareError,
@@ -62,6 +62,9 @@ export interface ShareDialogController {
62
62
  peopleWithAccess: string;
63
63
  addPeopleByEmail: string;
64
64
  notifyPeople: string;
65
+ addMessage: string;
66
+ hideMessage: string;
67
+ messagePlaceholder: string;
65
68
  role: string;
66
69
  remove: string;
67
70
  noAccess: string;
@@ -87,6 +90,10 @@ export interface ShareDialogController {
87
90
  notifyPeople: boolean;
88
91
  setNotifyPeople: (notify: boolean) => void;
89
92
  showNotifyPeople: boolean;
93
+ message: string;
94
+ setMessage: (message: string) => void;
95
+ messageOpen: boolean;
96
+ setMessageOpen: (open: boolean) => void;
90
97
  disabled: boolean;
91
98
  pending: boolean;
92
99
  submit: () => void;
@@ -18,6 +18,8 @@ export function useShareDialogController({ open, onClose, resourceType, resource
18
18
  const [email, setEmail] = useState("");
19
19
  const [role, setRole] = useState("viewer");
20
20
  const [notifyPeople, setNotifyPeople] = useState(true);
21
+ const [message, setMessage] = useState("");
22
+ const [messageOpen, setMessageOpen] = useState(false);
21
23
  const [copiedField, setCopiedField] = useState(null);
22
24
  const [visibilityOverride, setVisibilityOverride] = useState(null);
23
25
  const [mutationError, setMutationError] = useState(null);
@@ -100,6 +102,7 @@ export function useShareDialogController({ open, onClose, resourceType, resource
100
102
  const principalId = email.trim();
101
103
  if (!canManage || !principalId)
102
104
  return;
105
+ const notificationMessage = notifyPeople ? message.trim() : "";
103
106
  const optimistic = {
104
107
  id: `pending-${principalId}`,
105
108
  principalType: "user",
@@ -116,9 +119,12 @@ export function useShareDialogController({ open, onClose, resourceType, resource
116
119
  role,
117
120
  notify: notifyPeople,
118
121
  resourceUrl: getNotificationUrl(shareUrl),
122
+ ...(notificationMessage ? { message: notificationMessage } : {}),
119
123
  }, {
120
124
  onSuccess: () => {
121
125
  setEmail("");
126
+ setMessage("");
127
+ setMessageOpen(false);
122
128
  void refetch();
123
129
  },
124
130
  onError: (error) => {
@@ -129,6 +135,7 @@ export function useShareDialogController({ open, onClose, resourceType, resource
129
135
  }, [
130
136
  canManage,
131
137
  email,
138
+ message,
132
139
  notifyPeople,
133
140
  refetch,
134
141
  resourceId,
@@ -220,6 +227,9 @@ export function useShareDialogController({ open, onClose, resourceType, resource
220
227
  peopleWithAccess: t("share.peopleWithAccess"),
221
228
  addPeopleByEmail: t("share.addPeopleByEmail"),
222
229
  notifyPeople: t("share.notifyPeople"),
230
+ addMessage: t("share.addMessage"),
231
+ hideMessage: t("share.hideMessage"),
232
+ messagePlaceholder: t("share.messagePlaceholder"),
223
233
  role: t("share.role"),
224
234
  remove: t("share.remove"),
225
235
  noAccess: t("share.noAccess"),
@@ -245,6 +255,10 @@ export function useShareDialogController({ open, onClose, resourceType, resource
245
255
  notifyPeople,
246
256
  setNotifyPeople,
247
257
  showNotifyPeople: email.trim().length > 0,
258
+ message,
259
+ setMessage,
260
+ messageOpen,
261
+ setMessageOpen,
248
262
  disabled: !canManage || email.trim().length === 0,
249
263
  pending: shareMutation.isPending,
250
264
  submit: submitInvite,
@@ -1,4 +1,5 @@
1
1
  import { LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "../agent/engine/credential-errors.js";
2
+ import { emitChatFirstOpenApp } from "./chat-first.js";
2
3
  import { formatChatErrorText, normalizeChatError } from "./error-format.js";
3
4
  import { humanizeToolLabelText, humanizeToolName, runningToolLabel, } from "./tool-display.js";
4
5
  const INTERRUPTED_TOOL_RESULT = "Interrupted before this tool returned a result.";
@@ -869,6 +870,41 @@ function shouldDispatchStreamProgress(state) {
869
870
  state.streamProgressDispatched = true;
870
871
  return true;
871
872
  }
873
+ function emitFirstPartyOpenAppHandoff(ev) {
874
+ if (ev.type !== "tool_done" || (ev.tool ?? "unknown") !== "open_app") {
875
+ return;
876
+ }
877
+ if (ev.isError === true)
878
+ return;
879
+ if (!ev.result?.trim()) {
880
+ console.warn("[chat-first] open_app completed without a readable result; no app pane opened");
881
+ return;
882
+ }
883
+ let result;
884
+ try {
885
+ const parsed = JSON.parse(ev.result);
886
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
887
+ console.warn("[chat-first] open_app completed without a readable result; no app pane opened");
888
+ return;
889
+ }
890
+ result = parsed;
891
+ }
892
+ catch {
893
+ // coercion-ok: unreadable tool output must not be treated as a successful handoff.
894
+ console.warn("[chat-first] open_app completed without a readable result; no app pane opened");
895
+ return;
896
+ }
897
+ const readString = (value) => typeof value === "string" && value.trim() ? value : undefined;
898
+ const delivery = emitChatFirstOpenApp({
899
+ app: readString(result.app ?? result.appId ?? result.application),
900
+ path: readString(result.path ?? result.targetPath),
901
+ url: readString(result.url ?? result.href),
902
+ view: readString(result.view),
903
+ });
904
+ if (!delivery.delivered) {
905
+ console.warn(`[chat-first] open_app was completed but not delivered (${delivery.reason ?? "unknown"})`);
906
+ }
907
+ }
872
908
  /**
873
909
  * Process a single SSE event and update the content accumulator.
874
910
  * Returns: "continue" to keep going, "done" to stop, or a yield-ready result.
@@ -1124,6 +1160,7 @@ export function processEvent(ev, content, toolCallCounter, tabId, state) {
1124
1160
  if (findCompletedToolCallIndex(content, ev.id) >= 0) {
1125
1161
  return { action: "continue" };
1126
1162
  }
1163
+ emitFirstPartyOpenAppHandoff(ev);
1127
1164
  if (typeof window !== "undefined") {
1128
1165
  window.dispatchEvent(new CustomEvent("agent-native:tool-done", {
1129
1166
  detail: { tool: doneTool, result: ev.result },
@@ -15,6 +15,8 @@ interface UseSessionResult {
15
15
  /** Restart the resolve loop, e.g. from a "Try again" control. */
16
16
  retry: () => void;
17
17
  }
18
+ /** Tell mounted session consumers to re-check auth after a logout. */
19
+ export declare function notifySessionInvalidated(): void;
18
20
  /**
19
21
  * Client-side hook to get the current auth session.
20
22
  *
@@ -1,13 +1,18 @@
1
1
  import { useCallback, useEffect, useState } from "react";
2
2
  import { setSentryUser, trackSessionStatus } from "./analytics.js";
3
- import { fetchAuthSessionStatus } from "./client-status-requests.js";
3
+ import { fetchAuthSessionStatus, invalidateClientStatusRequest, } from "./client-status-requests.js";
4
4
  const SESSION_CACHE_TTL_MS = 30_000;
5
5
  const SESSION_RETRY_DELAY_MS = 1_000;
6
6
  const SESSION_MAX_ATTEMPTS = 4;
7
+ const SESSION_INVALIDATION_STORAGE_KEY = "agent-native:session-invalidated";
8
+ const SESSION_STATUS_PATH = "/_agent-native/auth/session";
7
9
  let cachedSession;
8
10
  let cachedSessionAt = 0;
9
11
  let sessionRequest;
10
12
  let trackedSessionIdentity;
13
+ let sessionGeneration = 0;
14
+ let sessionInvalidationListenersInstalled = false;
15
+ const sessionInvalidationSubscribers = new Set();
11
16
  function hasFreshSessionCache() {
12
17
  return (cachedSession !== undefined &&
13
18
  Date.now() - cachedSessionAt < SESSION_CACHE_TTL_MS);
@@ -29,16 +34,61 @@ function publishSessionIdentity(session) {
29
34
  }
30
35
  trackSessionStatus(Boolean(session));
31
36
  }
37
+ function invalidateSessionCache() {
38
+ sessionGeneration += 1;
39
+ cachedSession = undefined;
40
+ cachedSessionAt = 0;
41
+ sessionRequest = undefined;
42
+ invalidateClientStatusRequest(SESSION_STATUS_PATH);
43
+ for (const subscriber of sessionInvalidationSubscribers)
44
+ subscriber();
45
+ }
46
+ function installSessionInvalidationListeners() {
47
+ if (sessionInvalidationListenersInstalled ||
48
+ typeof window === "undefined" ||
49
+ typeof document === "undefined") {
50
+ return;
51
+ }
52
+ sessionInvalidationListenersInstalled = true;
53
+ window.addEventListener("focus", invalidateSessionCache);
54
+ window.addEventListener("storage", (event) => {
55
+ if (event.key === SESSION_INVALIDATION_STORAGE_KEY) {
56
+ invalidateSessionCache();
57
+ }
58
+ });
59
+ document.addEventListener("visibilitychange", () => {
60
+ if (document.visibilityState === "visible") {
61
+ invalidateSessionCache();
62
+ }
63
+ });
64
+ }
65
+ /** Tell mounted session consumers to re-check auth after a logout. */
66
+ export function notifySessionInvalidated() {
67
+ if (typeof window === "undefined")
68
+ return;
69
+ installSessionInvalidationListeners();
70
+ invalidateSessionCache();
71
+ try {
72
+ window.localStorage.setItem(SESSION_INVALIDATION_STORAGE_KEY, `${Date.now()}:${Math.random()}`);
73
+ }
74
+ catch (error) {
75
+ console.warn("Unable to broadcast session invalidation", error);
76
+ }
77
+ }
32
78
  function fetchSharedSession() {
33
79
  if (hasFreshSessionCache())
34
80
  return Promise.resolve(cachedSession ?? null);
35
81
  if (sessionRequest)
36
82
  return sessionRequest;
37
- sessionRequest = (async () => {
83
+ const requestGeneration = sessionGeneration;
84
+ let request;
85
+ const requestResult = (async () => {
38
86
  try {
39
87
  const result = await fetchAuthSessionStatus();
40
88
  if (result.state === "unavailable")
41
89
  return undefined;
90
+ if (requestGeneration !== sessionGeneration)
91
+ return undefined;
42
92
  const data = result.value;
43
93
  const session = data.error ? null : data;
44
94
  cachedSession = session;
@@ -49,9 +99,12 @@ function fetchSharedSession() {
49
99
  catch {
50
100
  return undefined;
51
101
  }
52
- })().finally(() => {
53
- sessionRequest = undefined;
102
+ })();
103
+ request = requestResult.finally(() => {
104
+ if (sessionRequest === request)
105
+ sessionRequest = undefined;
54
106
  });
107
+ sessionRequest = request;
55
108
  return sessionRequest;
56
109
  }
57
110
  /**
@@ -64,6 +117,7 @@ function fetchSharedSession() {
64
117
  * Templates should use this instead of building their own auth context.
65
118
  */
66
119
  export function useSession() {
120
+ installSessionInvalidationListeners();
67
121
  const cached = hasFreshSessionCache() ? (cachedSession ?? null) : null;
68
122
  const [session, setSession] = useState(cached);
69
123
  const [status, setStatus] = useState(() => {
@@ -73,11 +127,21 @@ export function useSession() {
73
127
  });
74
128
  const [error, setError] = useState(null);
75
129
  const [retryToken, setRetryToken] = useState(0);
130
+ const [invalidationToken, setInvalidationToken] = useState(0);
76
131
  const retry = useCallback(() => {
77
132
  setError(null);
78
133
  setStatus("loading");
79
134
  setRetryToken((token) => token + 1);
80
135
  }, []);
136
+ useEffect(() => {
137
+ const subscriber = () => {
138
+ setInvalidationToken((token) => token + 1);
139
+ };
140
+ sessionInvalidationSubscribers.add(subscriber);
141
+ return () => {
142
+ sessionInvalidationSubscribers.delete(subscriber);
143
+ };
144
+ }, []);
81
145
  useEffect(() => {
82
146
  let cancelled = false;
83
147
  let retryTimer;
@@ -108,7 +172,7 @@ export function useSession() {
108
172
  if (retryTimer)
109
173
  clearTimeout(retryTimer);
110
174
  };
111
- }, [retryToken]);
175
+ }, [invalidationToken, retryToken]);
112
176
  // Callers that only read `isLoading`/`session` (most of the codebase, not
113
177
  // yet migrated to `status`) must not see "unavailable" as "signed out" —
114
178
  // that bounces an authenticated user through sign-in-only UI over a
@@ -13,8 +13,8 @@
13
13
  * Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
14
14
  */
15
15
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- ok?: undefined;
17
16
  error: string;
17
+ ok?: undefined;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
@@ -1452,7 +1452,7 @@ export function generateCloudflarePagesStaticShellFromManifest(manifest, basePat
1452
1452
  ? DEFAULT_ROOT_LOADER_REACT_ROUTER_TURBO_STREAM
1453
1453
  : EMPTY_REACT_ROUTER_TURBO_STREAM;
1454
1454
  // guard:allow-raw-color - static shell loads before app theme tokens exist
1455
- return `<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/><link rel="manifest" href="/manifest.json"/><link rel="icon" type="image/svg+xml" href="/favicon.svg"/>${modulePreloads}${stylesheets}</head><body><div style="display:flex;align-items:center;justify-content:center;height:100vh;width:100%"><svg role="status" aria-label="Loading" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="animation:an-spin 1s linear infinite;opacity:0.7"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg><style>@keyframes an-spin { to { transform: rotate(360deg) } } @media (prefers-color-scheme: dark) { html { background: #09090b; color: #fafafa } }</style></div><script>window.__reactRouterContext = ${JSON.stringify(context)};window.__reactRouterContext.stream = new ReadableStream({start(controller){window.__reactRouterContext.streamController = controller;}}).pipeThrough(new TextEncoderStream());</script><script type="module" async="">${routeModuleScript}</script><!--$--><script>window.__reactRouterContext.streamController.enqueue(${JSON.stringify(encodedInitialState)});</script><!--$--><script>window.__reactRouterContext.streamController.close();</script><!--/$--><!--/$--></body></html>`;
1455
+ return `<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/><link rel="icon" type="image/svg+xml" href="/favicon.svg"/>${modulePreloads}${stylesheets}</head><body><div style="display:flex;align-items:center;justify-content:center;height:100vh;width:100%"><svg role="status" aria-label="Loading" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="animation:an-spin 1s linear infinite;opacity:0.7"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg><style>@keyframes an-spin { to { transform: rotate(360deg) } } @media (prefers-color-scheme: dark) { html { background: #09090b; color: #fafafa } }</style></div><script>window.__reactRouterContext = ${JSON.stringify(context)};window.__reactRouterContext.stream = new ReadableStream({start(controller){window.__reactRouterContext.streamController = controller;}}).pipeThrough(new TextEncoderStream());</script><script type="module" async="">${routeModuleScript}</script><!--$--><script>window.__reactRouterContext.streamController.enqueue(${JSON.stringify(encodedInitialState)});</script><!--$--><script>window.__reactRouterContext.streamController.close();</script><!--/$--><!--/$--></body></html>`;
1456
1456
  }
1457
1457
  function writeCloudflarePagesStaticShell({ serverDir, distDir, tmpDir, }) {
1458
1458
  const serverEntry = path.join(serverDir, "index.js");
@@ -27,7 +27,7 @@ export interface PreUploadAttachmentsResult {
27
27
  /** Set when at least one image was uploaded. List of hosted URLs the agent
28
28
  * can embed in HTML, slide content, documents, etc. */
29
29
  uploaded: PreUploadedImageAttachment[];
30
- /** Uploaded non-image files (PDF, generic binary). Parallel to `uploaded`
30
+ /** Uploaded non-image files (documents, spreadsheets, generic binary). Parallel to `uploaded`
31
31
  * but for the file/document attachment type. */
32
32
  uploadedFiles: PreUploadedFileAttachment[];
33
33
  /** True if at least one image or file could not be uploaded because no
@@ -39,8 +39,7 @@ export interface PreUploadAttachmentsResult {
39
39
  /** The first provider error, bounded for safe inclusion in the chat hint. */
40
40
  uploadError?: string;
41
41
  /** A pre-formatted block to inject into the user message text so the agent
42
- * has each hosted URL inline. Null when nothing was uploaded or no provider
43
- * is configured. */
42
+ * has hosted URLs and bounded derived source context inline. */
44
43
  injectedText: string | null;
45
44
  }
46
45
  /**
@@ -63,7 +62,7 @@ export declare function preUploadImageAttachments(opts: {
63
62
  ownerEmail: string | null | undefined;
64
63
  }): Promise<PreUploadAttachmentsResult>;
65
64
  /**
66
- * Pre-upload ALL chat attachments (images AND files/PDFs) through the active
65
+ * Pre-upload ALL chat attachments (images AND files) through the active
67
66
  * file-upload provider. When a provider is configured, each attachment gets a
68
67
  * `url` property injected so downstream code can store/send URLs instead of
69
68
  * base64. The base64 data is kept in-memory for the current turn so vision and