@agent-native/core 0.152.1 → 0.153.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 (907) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/adding-a-feature/SKILL.md +208 -0
  3. package/corpus/templates/analytics/.agents/skills/context-awareness/SKILL.md +206 -0
  4. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +6 -4
  5. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +73 -80
  6. package/corpus/templates/analytics/.agents/skills/portability/SKILL.md +107 -0
  7. package/corpus/templates/analytics/.agents/skills/secrets/SKILL.md +339 -0
  8. package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +3 -3
  9. package/corpus/templates/analytics/.agents/skills/shadcn-ui/SKILL.md +1 -1
  10. package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +1 -0
  11. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +116 -44
  12. package/corpus/templates/analytics/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  13. package/corpus/templates/analytics/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  14. package/corpus/templates/analytics/.agents/skills/workspace-conventions/SKILL.md +93 -0
  15. package/corpus/templates/analytics/actions/data-source-status.ts +4 -1
  16. package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +2 -2
  17. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +7 -2
  18. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +7 -1
  19. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +30 -32
  20. package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +10 -0
  21. package/corpus/templates/analytics/app/global.css +5 -1
  22. package/corpus/templates/analytics/app/lib/data-source-status.ts +3 -3
  23. package/corpus/templates/analytics/app/lib/resource-access.ts +6 -1
  24. package/corpus/templates/analytics/app/pages/DataSources.tsx +79 -51
  25. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +1 -0
  26. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +1 -0
  27. package/corpus/templates/analytics/changelog/2026-08-12-analytics-sidebar-ask-toggle-and-compact-rail.md +6 -0
  28. package/corpus/templates/analytics/changelog/2026-08-12-data-source-statuses-now-distinguish-workspace-access-from-c.md +6 -0
  29. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +559 -147
  30. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +79 -5
  31. package/corpus/templates/analytics/server/plugins/db.ts +84 -0
  32. package/corpus/templates/assets/.agents/skills/adding-a-feature/SKILL.md +208 -0
  33. package/corpus/templates/assets/.agents/skills/context-awareness/SKILL.md +206 -0
  34. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +73 -80
  35. package/corpus/templates/assets/.agents/skills/portability/SKILL.md +107 -0
  36. package/corpus/templates/assets/.agents/skills/secrets/SKILL.md +339 -0
  37. package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +3 -3
  38. package/corpus/templates/assets/.agents/skills/shadcn-ui/SKILL.md +1 -1
  39. package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +1 -0
  40. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +116 -44
  41. package/corpus/templates/assets/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  42. package/corpus/templates/assets/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  43. package/corpus/templates/assets/.agents/skills/workspace-conventions/SKILL.md +93 -0
  44. package/corpus/templates/assets/app/components/asset/AssetPreviewDialog.tsx +13 -5
  45. package/corpus/templates/assets/app/lib/asset-urls.ts +13 -0
  46. package/corpus/templates/assets/app/routes/asset.$id.tsx +16 -6
  47. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +1 -0
  48. package/corpus/templates/assets/app/routes/library.tsx +336 -78
  49. package/corpus/templates/assets/shared/api.ts +1 -1
  50. package/corpus/templates/brain/.agents/skills/adding-a-feature/SKILL.md +3 -2
  51. package/corpus/templates/brain/.agents/skills/brain/SKILL.md +8 -6
  52. package/corpus/templates/brain/.agents/skills/context-awareness/SKILL.md +206 -0
  53. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +73 -80
  54. package/corpus/templates/brain/.agents/skills/ingestion-and-connectors/SKILL.md +7 -0
  55. package/corpus/templates/brain/.agents/skills/portability/SKILL.md +107 -0
  56. package/corpus/templates/brain/.agents/skills/secrets/SKILL.md +339 -0
  57. package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +3 -3
  58. package/corpus/templates/brain/.agents/skills/shadcn-ui/SKILL.md +1 -1
  59. package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +1 -0
  60. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +116 -44
  61. package/corpus/templates/brain/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  62. package/corpus/templates/brain/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  63. package/corpus/templates/brain/.agents/skills/workspace-conventions/SKILL.md +93 -0
  64. package/corpus/templates/brain/AGENTS.md +2 -1
  65. package/corpus/templates/brain/actions/ask-brain.ts +19 -16
  66. package/corpus/templates/brain/actions/create-source.ts +11 -2
  67. package/corpus/templates/brain/actions/import-markdown-files.ts +237 -0
  68. package/corpus/templates/brain/actions/resanitize-captures.ts +3 -1
  69. package/corpus/templates/brain/actions/test-slack-connection.ts +12 -3
  70. package/corpus/templates/brain/actions/update-source.ts +10 -1
  71. package/corpus/templates/brain/app/components/brain/Surface.tsx +1 -11
  72. package/corpus/templates/brain/app/i18n-data.ts +111 -0
  73. package/corpus/templates/brain/app/lib/brain.ts +23 -1
  74. package/corpus/templates/brain/app/routes/knowledge.tsx +0 -2
  75. package/corpus/templates/brain/app/routes/ops.tsx +0 -2
  76. package/corpus/templates/brain/app/routes/review.tsx +0 -2
  77. package/corpus/templates/brain/app/routes/search.tsx +0 -2
  78. package/corpus/templates/brain/app/routes/sources.tsx +354 -6
  79. package/corpus/templates/brain/server/lib/brain-response-guard.ts +63 -1
  80. package/corpus/templates/brain/server/lib/brain.ts +9 -6
  81. package/corpus/templates/brain/server/lib/connectors.ts +20 -6
  82. package/corpus/templates/brain/server/lib/search.ts +9 -1
  83. package/corpus/templates/brain/server/lib/source-credentials.ts +95 -57
  84. package/corpus/templates/brain/server/plugins/agent-chat.ts +5 -2
  85. package/corpus/templates/brain/server/plugins/auth.ts +2 -0
  86. package/corpus/templates/calendar/.agents/skills/adding-a-feature/SKILL.md +208 -0
  87. package/corpus/templates/calendar/.agents/skills/context-awareness/SKILL.md +206 -0
  88. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +73 -80
  89. package/corpus/templates/calendar/.agents/skills/portability/SKILL.md +107 -0
  90. package/corpus/templates/calendar/.agents/skills/secrets/SKILL.md +339 -0
  91. package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +3 -3
  92. package/corpus/templates/calendar/.agents/skills/shadcn-ui/SKILL.md +1 -1
  93. package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +1 -0
  94. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +116 -44
  95. package/corpus/templates/calendar/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  96. package/corpus/templates/calendar/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  97. package/corpus/templates/calendar/.agents/skills/workspace-conventions/SKILL.md +93 -0
  98. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -0
  99. package/corpus/templates/chat/.agents/skills/adding-a-feature/SKILL.md +3 -2
  100. package/corpus/templates/chat/.agents/skills/context-awareness/SKILL.md +206 -0
  101. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +73 -80
  102. package/corpus/templates/chat/.agents/skills/portability/SKILL.md +107 -0
  103. package/corpus/templates/chat/.agents/skills/secrets/SKILL.md +339 -0
  104. package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +3 -3
  105. package/corpus/templates/chat/.agents/skills/shadcn-ui/SKILL.md +1 -1
  106. package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +1 -0
  107. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +116 -44
  108. package/corpus/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  109. package/corpus/templates/chat/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  110. package/corpus/templates/chat/.agents/skills/workspace-conventions/SKILL.md +93 -0
  111. package/corpus/templates/chat/AGENTS.md +4 -5
  112. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +73 -80
  113. package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +1 -1
  114. package/corpus/templates/clips/.agents/skills/secrets/SKILL.md +339 -0
  115. package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +3 -3
  116. package/corpus/templates/clips/.agents/skills/shadcn-ui/SKILL.md +1 -1
  117. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +116 -44
  118. package/corpus/templates/clips/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  119. package/corpus/templates/clips/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  120. package/corpus/templates/clips/.agents/skills/workspace-conventions/SKILL.md +93 -0
  121. package/corpus/templates/clips/actions/add-comment.ts +1 -2
  122. package/corpus/templates/clips/actions/delete-comment.ts +2 -0
  123. package/corpus/templates/clips/actions/get-recording-insights.ts +9 -8
  124. package/corpus/templates/clips/actions/get-recording-player-data.ts +4 -1
  125. package/corpus/templates/clips/actions/react-to-comment.ts +1 -1
  126. package/corpus/templates/clips/actions/react-to-recording.ts +1 -1
  127. package/corpus/templates/clips/actions/reply-to-comment.ts +1 -1
  128. package/corpus/templates/clips/actions/resolve-comment.ts +1 -2
  129. package/corpus/templates/clips/actions/update-comment.ts +1 -1
  130. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +1 -0
  131. package/corpus/templates/clips/app/components/editor/thumbnail-picker.tsx +59 -16
  132. package/corpus/templates/clips/app/components/player/comments-panel.tsx +88 -39
  133. package/corpus/templates/clips/app/components/player/share-dialog.tsx +12 -8
  134. package/corpus/templates/clips/app/components/player/timestamped-comment-button.tsx +3 -1
  135. package/corpus/templates/clips/app/components/player/video-player.tsx +1 -1
  136. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +2 -1
  137. package/corpus/templates/clips/app/i18n/en-US.ts +2 -1
  138. package/corpus/templates/clips/app/lib/public-meeting.ts +1 -1
  139. package/corpus/templates/clips/app/lib/thumbnail-capture.ts +71 -0
  140. package/corpus/templates/clips/app/lib/thumbnail-picker-state.ts +93 -0
  141. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +1 -1
  142. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +25 -19
  143. package/corpus/templates/clips/app/routes/share.$shareId.tsx +8 -0
  144. package/corpus/templates/clips/changelog/2026-07-23-meeting-recording-meters-and-dismissed-prompts.md +1 -1
  145. package/corpus/templates/clips/changelog/2026-08-11-clips-asks-to-take-notes-again-when-you-join-an-adhoc-zoom-o.md +6 -0
  146. package/corpus/templates/clips/changelog/2026-08-12-viewers-are-read-only-and-commenters-can-comment.md +6 -0
  147. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +166 -14
  148. package/corpus/templates/clips/desktop/src-tauri/src/call_activity.rs +146 -0
  149. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
  150. package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +2 -122
  151. package/corpus/templates/clips/server/lib/recording-page-access.ts +6 -1
  152. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +5 -0
  153. package/corpus/templates/clips/shared/share-meta.ts +19 -3
  154. package/corpus/templates/content/.agents/skills/adding-a-feature/SKILL.md +208 -0
  155. package/corpus/templates/content/.agents/skills/context-awareness/SKILL.md +206 -0
  156. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +73 -80
  157. package/corpus/templates/content/.agents/skills/portability/SKILL.md +107 -0
  158. package/corpus/templates/content/.agents/skills/secrets/SKILL.md +339 -0
  159. package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +3 -3
  160. package/corpus/templates/content/.agents/skills/shadcn-ui/SKILL.md +1 -1
  161. package/corpus/templates/content/.agents/skills/sharing/SKILL.md +1 -0
  162. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +116 -44
  163. package/corpus/templates/content/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  164. package/corpus/templates/content/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  165. package/corpus/templates/content/.agents/skills/workspace-conventions/SKILL.md +93 -0
  166. package/corpus/templates/content/actions/_database-utils.ts +10 -0
  167. package/corpus/templates/content/actions/add-comment.ts +1 -1
  168. package/corpus/templates/content/actions/attach-content-database-source.ts +158 -61
  169. package/corpus/templates/content/actions/create-document.ts +1 -0
  170. package/corpus/templates/content/actions/delete-comment.ts +1 -1
  171. package/corpus/templates/content/actions/get-document.ts +9 -1
  172. package/corpus/templates/content/actions/import-content-source.ts +8 -0
  173. package/corpus/templates/content/actions/list-documents.ts +10 -1
  174. package/corpus/templates/content/actions/update-comment.ts +1 -1
  175. package/corpus/templates/content/actions/update-document.ts +11 -0
  176. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +52 -29
  177. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +13 -8
  178. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +62 -2
  179. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +163 -53
  180. package/corpus/templates/content/app/components/editor/database/TimelineView.tsx +16 -0
  181. package/corpus/templates/content/app/components/editor/database/settings.tsx +22 -13
  182. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +7 -7
  183. package/corpus/templates/content/app/hooks/use-documents.ts +2 -0
  184. package/corpus/templates/content/app/i18n-data.ts +10 -0
  185. package/corpus/templates/content/app/root.tsx +1 -21
  186. package/corpus/templates/content/changelog/2026-08-05-connect-sources-from-add-column.md +6 -0
  187. package/corpus/templates/content/changelog/2026-08-12-viewers-are-read-only-and-commenters-can-comment.md +6 -0
  188. package/corpus/templates/content/shared/api.ts +7 -1
  189. package/corpus/templates/crm/.agents/skills/actions/SKILL.md +146 -0
  190. package/corpus/templates/crm/.agents/skills/actions/references/action-fields.md +136 -0
  191. package/corpus/templates/crm/.agents/skills/actions/references/examples.md +94 -0
  192. package/corpus/templates/crm/.agents/skills/actions/references/provider-apis.md +95 -0
  193. package/corpus/templates/crm/.agents/skills/adding-a-feature/SKILL.md +208 -0
  194. package/corpus/templates/crm/.agents/skills/context-awareness/SKILL.md +206 -0
  195. package/corpus/templates/crm/.agents/skills/delegate-to-agent/SKILL.md +289 -0
  196. package/corpus/templates/crm/.agents/skills/frontend-design/SKILL.md +296 -0
  197. package/corpus/templates/crm/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  198. package/corpus/templates/crm/.agents/skills/portability/SKILL.md +107 -0
  199. package/corpus/templates/crm/.agents/skills/real-time-sync/SKILL.md +232 -0
  200. package/corpus/templates/crm/.agents/skills/secrets/SKILL.md +339 -0
  201. package/corpus/templates/crm/.agents/skills/security/SKILL.md +324 -0
  202. package/corpus/templates/crm/.agents/skills/self-modifying-code/SKILL.md +123 -0
  203. package/corpus/templates/crm/.agents/skills/shadcn-ui/SKILL.md +123 -0
  204. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +1 -0
  205. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +116 -44
  206. package/corpus/templates/crm/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  207. package/corpus/templates/crm/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  208. package/corpus/templates/crm/.agents/skills/workspace-conventions/SKILL.md +93 -0
  209. package/corpus/templates/design/.agents/skills/adding-a-feature/SKILL.md +208 -0
  210. package/corpus/templates/design/.agents/skills/context-awareness/SKILL.md +206 -0
  211. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +73 -80
  212. package/corpus/templates/design/.agents/skills/portability/SKILL.md +107 -0
  213. package/corpus/templates/design/.agents/skills/real-time-sync/SKILL.md +232 -0
  214. package/corpus/templates/design/.agents/skills/secrets/SKILL.md +339 -0
  215. package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +3 -3
  216. package/corpus/templates/design/.agents/skills/shadcn-ui/SKILL.md +1 -1
  217. package/corpus/templates/design/.agents/skills/sharing/SKILL.md +1 -0
  218. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +116 -44
  219. package/corpus/templates/design/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  220. package/corpus/templates/design/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  221. package/corpus/templates/design/.agents/skills/workspace-conventions/SKILL.md +93 -0
  222. package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -2
  223. package/corpus/templates/design/app/components/design-system/ProductionDesignSystemShowcase.tsx +6 -18
  224. package/corpus/templates/design/app/i18n/en-US.ts +1 -1
  225. package/corpus/templates/design/app/i18n-data.ts +40 -0
  226. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +153 -131
  227. package/corpus/templates/design/app/pages/DesignSystems.tsx +7 -61
  228. package/corpus/templates/design/app/pages/Present.tsx +8 -2
  229. package/corpus/templates/design/app/pages/Templates.tsx +1 -0
  230. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +4 -1
  231. package/corpus/templates/design/app/pages/design-editor/types.ts +6 -1
  232. package/corpus/templates/design/changelog/2026-08-12-viewers-are-read-only-and-commenters-can-comment.md +6 -0
  233. package/corpus/templates/dispatch/.agents/skills/adding-a-feature/SKILL.md +208 -0
  234. package/corpus/templates/dispatch/.agents/skills/context-awareness/SKILL.md +206 -0
  235. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +73 -80
  236. package/corpus/templates/dispatch/.agents/skills/portability/SKILL.md +107 -0
  237. package/corpus/templates/dispatch/.agents/skills/real-time-sync/SKILL.md +232 -0
  238. package/corpus/templates/dispatch/.agents/skills/secrets/SKILL.md +339 -0
  239. package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +3 -3
  240. package/corpus/templates/dispatch/.agents/skills/shadcn-ui/SKILL.md +1 -1
  241. package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +1 -0
  242. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +116 -44
  243. package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  244. package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  245. package/corpus/templates/dispatch/.agents/skills/workspace-conventions/SKILL.md +93 -0
  246. package/corpus/templates/dispatch/actions/apply-workspace-connection-setup.ts +97 -15
  247. package/corpus/templates/dispatch/actions/connection-permissions.ts +77 -0
  248. package/corpus/templates/dispatch/actions/delete-workspace-connection.ts +12 -2
  249. package/corpus/templates/dispatch/actions/list-workspace-user-groups.ts +19 -0
  250. package/corpus/templates/dispatch/actions/plan-workspace-connection-setup.ts +9 -2
  251. package/corpus/templates/dispatch/actions/set-workspace-connection-grant.ts +21 -2
  252. package/corpus/templates/dispatch/actions/upsert-workspace-connection.ts +66 -1
  253. package/corpus/templates/dispatch/actions/upsert-workspace-user-group.ts +27 -0
  254. package/corpus/templates/dispatch/app/chat-first-pages.ts +84 -0
  255. package/corpus/templates/dispatch/app/i18n-data.ts +3989 -3527
  256. package/corpus/templates/dispatch/app/routes/integrations.tsx +1568 -685
  257. package/corpus/templates/dispatch/app/routes/settings.tsx +14 -9
  258. package/corpus/templates/dispatch/changelog/2026-08-12-search-and-pin-workspace-apps-from-the-app-catalog.md +6 -0
  259. package/corpus/templates/dispatch/changelog/2026-08-12-workspace-integration-access-now-shows-exactly-which-apps-ca.md +6 -0
  260. package/corpus/templates/dispatch/server/lib/app-roles.ts +5 -0
  261. package/corpus/templates/dispatch/shared/app-roles.ts +1 -0
  262. package/corpus/templates/factory/.agents/skills/adding-a-feature/SKILL.md +3 -2
  263. package/corpus/templates/factory/.agents/skills/context-awareness/SKILL.md +206 -0
  264. package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +73 -80
  265. package/corpus/templates/factory/.agents/skills/portability/SKILL.md +107 -0
  266. package/corpus/templates/factory/.agents/skills/secrets/SKILL.md +339 -0
  267. package/corpus/templates/factory/.agents/skills/self-modifying-code/SKILL.md +3 -3
  268. package/corpus/templates/factory/.agents/skills/shadcn-ui/SKILL.md +1 -1
  269. package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +1 -0
  270. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +116 -44
  271. package/corpus/templates/factory/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  272. package/corpus/templates/factory/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  273. package/corpus/templates/factory/.agents/skills/workspace-conventions/SKILL.md +93 -0
  274. package/corpus/templates/factory/AGENTS.md +10 -1
  275. package/corpus/templates/factory/README.md +18 -7
  276. package/corpus/templates/factory/actions/list-triage-items.ts +10 -4
  277. package/corpus/templates/factory/actions/poll-github-sources.ts +53 -14
  278. package/corpus/templates/factory/actions/poll-sentry-errors.ts +31 -13
  279. package/corpus/templates/factory/actions/poll-slack-channel.ts +41 -5
  280. package/corpus/templates/factory/actions/provider-api-catalog.ts +13 -0
  281. package/corpus/templates/factory/actions/provider-api-docs.ts +11 -0
  282. package/corpus/templates/factory/actions/provider-api-request.ts +17 -0
  283. package/corpus/templates/factory/actions/save-triage-config.ts +87 -38
  284. package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +14 -12
  285. package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +60 -77
  286. package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +66 -125
  287. package/corpus/templates/factory/app/components/layout/Header.tsx +1 -0
  288. package/corpus/templates/factory/app/components/layout/Layout.tsx +8 -3
  289. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +9 -5
  290. package/corpus/templates/factory/app/components/ui/input.tsx +15 -1
  291. package/corpus/templates/factory/app/components/ui/popover.tsx +1 -0
  292. package/corpus/templates/factory/app/components/ui/switch.tsx +1 -0
  293. package/corpus/templates/factory/app/components/ui/textarea.tsx +20 -1
  294. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +5 -3
  295. package/corpus/templates/factory/app/i18n/en-US.ts +17 -0
  296. package/corpus/templates/factory/app/routes/_index.tsx +5 -87
  297. package/corpus/templates/factory/app/routes/chat.$threadId.tsx +1 -1
  298. package/corpus/templates/factory/app/routes/chat.tsx +91 -0
  299. package/corpus/templates/factory/app/routes/factory.tsx +554 -427
  300. package/corpus/templates/factory/app/routes/settings.tsx +1 -1
  301. package/corpus/templates/factory/server/connectors/credentials.ts +25 -0
  302. package/corpus/templates/factory/server/lib/provider-api.ts +34 -0
  303. package/corpus/templates/factory/server/plugins/agent-chat.ts +15 -3
  304. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +96 -24
  305. package/corpus/templates/factory/server/triage/review-state.ts +33 -0
  306. package/corpus/templates/factory/server/triage/slack-poller.ts +1 -0
  307. package/corpus/templates/forms/.agents/skills/adding-a-feature/SKILL.md +208 -0
  308. package/corpus/templates/forms/.agents/skills/context-awareness/SKILL.md +206 -0
  309. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +73 -80
  310. package/corpus/templates/forms/.agents/skills/portability/SKILL.md +107 -0
  311. package/corpus/templates/forms/.agents/skills/secrets/SKILL.md +339 -0
  312. package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +3 -3
  313. package/corpus/templates/forms/.agents/skills/shadcn-ui/SKILL.md +1 -1
  314. package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +1 -0
  315. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +116 -44
  316. package/corpus/templates/forms/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  317. package/corpus/templates/forms/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  318. package/corpus/templates/forms/.agents/skills/workspace-conventions/SKILL.md +93 -0
  319. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +1 -0
  320. package/corpus/templates/forms/shared/types.ts +1 -1
  321. package/corpus/templates/macros/.agents/skills/adding-a-feature/SKILL.md +208 -0
  322. package/corpus/templates/macros/.agents/skills/context-awareness/SKILL.md +206 -0
  323. package/corpus/templates/macros/.agents/skills/delegate-to-agent/SKILL.md +289 -0
  324. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +73 -80
  325. package/corpus/templates/macros/.agents/skills/portability/SKILL.md +107 -0
  326. package/corpus/templates/macros/.agents/skills/real-time-sync/SKILL.md +232 -0
  327. package/corpus/templates/macros/.agents/skills/secrets/SKILL.md +339 -0
  328. package/corpus/templates/macros/.agents/skills/self-modifying-code/SKILL.md +123 -0
  329. package/corpus/templates/macros/.agents/skills/shadcn-ui/SKILL.md +1 -1
  330. package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +1 -0
  331. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +116 -44
  332. package/corpus/templates/macros/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  333. package/corpus/templates/macros/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  334. package/corpus/templates/macros/.agents/skills/workspace-conventions/SKILL.md +93 -0
  335. package/corpus/templates/mail/.agents/skills/adding-a-feature/SKILL.md +208 -0
  336. package/corpus/templates/mail/.agents/skills/context-awareness/SKILL.md +206 -0
  337. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +73 -80
  338. package/corpus/templates/mail/.agents/skills/portability/SKILL.md +107 -0
  339. package/corpus/templates/mail/.agents/skills/secrets/SKILL.md +339 -0
  340. package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +3 -3
  341. package/corpus/templates/mail/.agents/skills/shadcn-ui/SKILL.md +1 -1
  342. package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +1 -0
  343. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +116 -44
  344. package/corpus/templates/mail/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  345. package/corpus/templates/mail/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  346. package/corpus/templates/mail/.agents/skills/workspace-conventions/SKILL.md +93 -0
  347. package/corpus/templates/mail/actions/send-email.ts +9 -0
  348. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +4 -4
  349. package/corpus/templates/mail/changelog/2026-08-12-agent-sent-messages-appear-immediately.md +6 -0
  350. package/corpus/templates/plan/.agents/skills/adding-a-feature/SKILL.md +3 -2
  351. package/corpus/templates/plan/.agents/skills/context-awareness/SKILL.md +206 -0
  352. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +73 -80
  353. package/corpus/templates/plan/.agents/skills/portability/SKILL.md +107 -0
  354. package/corpus/templates/plan/.agents/skills/secrets/SKILL.md +339 -0
  355. package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +3 -3
  356. package/corpus/templates/plan/.agents/skills/shadcn-ui/SKILL.md +1 -1
  357. package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +1 -0
  358. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +116 -44
  359. package/corpus/templates/plan/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  360. package/corpus/templates/plan/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  361. package/corpus/templates/plan/.agents/skills/workspace-conventions/SKILL.md +93 -0
  362. package/corpus/templates/plan/actions/delete-plan-comment.ts +6 -0
  363. package/corpus/templates/plan/actions/reply-to-plan-comment.ts +7 -1
  364. package/corpus/templates/plan/actions/resolve-plan-comment.ts +7 -1
  365. package/corpus/templates/plan/actions/update-visual-plan.ts +6 -0
  366. package/corpus/templates/plan/app/hooks/use-plans.ts +1 -1
  367. package/corpus/templates/plan/app/pages/PlansPage.tsx +51 -23
  368. package/corpus/templates/plan/changelog/2026-08-12-viewers-are-read-only-and-commenters-can-comment.md +6 -0
  369. package/corpus/templates/plan/server/plans.ts +2 -2
  370. package/corpus/templates/plan/shared/types.ts +1 -1
  371. package/corpus/templates/slides/.agents/skills/adding-a-feature/SKILL.md +208 -0
  372. package/corpus/templates/slides/.agents/skills/context-awareness/SKILL.md +206 -0
  373. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +6 -0
  374. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +73 -80
  375. package/corpus/templates/slides/.agents/skills/portability/SKILL.md +107 -0
  376. package/corpus/templates/slides/.agents/skills/real-time-sync/SKILL.md +232 -0
  377. package/corpus/templates/slides/.agents/skills/secrets/SKILL.md +339 -0
  378. package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +3 -3
  379. package/corpus/templates/slides/.agents/skills/shadcn-ui/SKILL.md +1 -1
  380. package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +1 -0
  381. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +116 -44
  382. package/corpus/templates/slides/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  383. package/corpus/templates/slides/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  384. package/corpus/templates/slides/.agents/skills/workspace-conventions/SKILL.md +93 -0
  385. package/corpus/templates/slides/AGENTS.md +4 -0
  386. package/corpus/templates/slides/actions/add-slide-comment.ts +1 -1
  387. package/corpus/templates/slides/actions/delete-slide-comment.ts +1 -1
  388. package/corpus/templates/slides/actions/get-layout-overflows.ts +67 -2
  389. package/corpus/templates/slides/actions/update-slide-comment.ts +1 -1
  390. package/corpus/templates/slides/actions/view-screen.ts +76 -26
  391. package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +30 -8
  392. package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +2 -1
  393. package/corpus/templates/slides/app/components/editor/AlignmentGuides.tsx +75 -0
  394. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +80 -80
  395. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +125 -10
  396. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +244 -9
  397. package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +1 -1
  398. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +3 -3
  399. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +218 -0
  400. package/corpus/templates/slides/app/context/DeckContext.tsx +56 -30
  401. package/corpus/templates/slides/app/hooks/use-deck-role.ts +13 -5
  402. package/corpus/templates/slides/app/hooks/use-design-systems.ts +1 -1
  403. package/corpus/templates/slides/app/i18n/en-US.ts +6 -2
  404. package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -0
  405. package/corpus/templates/slides/app/lib/pending-deck-changes.ts +32 -0
  406. package/corpus/templates/slides/app/pages/DeckEditor.tsx +85 -4
  407. package/corpus/templates/slides/app/pages/Index.tsx +65 -66
  408. package/corpus/templates/slides/changelog/2026-08-12-viewers-are-read-only-and-commenters-can-comment.md +6 -0
  409. package/corpus/templates/tasks/.agents/skills/adding-a-feature/SKILL.md +3 -2
  410. package/corpus/templates/tasks/.agents/skills/context-awareness/SKILL.md +206 -0
  411. package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +73 -80
  412. package/corpus/templates/tasks/.agents/skills/portability/SKILL.md +107 -0
  413. package/corpus/templates/tasks/.agents/skills/secrets/SKILL.md +339 -0
  414. package/corpus/templates/tasks/.agents/skills/self-modifying-code/SKILL.md +3 -3
  415. package/corpus/templates/tasks/.agents/skills/shadcn-ui/SKILL.md +1 -1
  416. package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +1 -0
  417. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +116 -44
  418. package/corpus/templates/tasks/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  419. package/corpus/templates/tasks/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  420. package/corpus/templates/tasks/.agents/skills/workspace-conventions/SKILL.md +93 -0
  421. package/corpus/templates/tasks/app/components/custom-fields/FieldsPage.tsx +1 -4
  422. package/corpus/templates/tasks/app/components/inbox/InboxList.tsx +0 -1
  423. package/corpus/templates/tasks/app/components/inbox/InboxListPage.tsx +0 -1
  424. package/corpus/templates/tasks/app/components/shared/ListViewHeader.tsx +0 -3
  425. package/corpus/templates/tasks/app/components/shared/PageHeader.tsx +1 -4
  426. package/corpus/templates/tasks/app/components/tasks/TaskList.tsx +0 -1
  427. package/corpus/templates/tasks/app/components/tasks/TaskListPage.tsx +0 -1
  428. package/dist/agent/engine/ai-sdk-engine.js +5 -0
  429. package/dist/agent/engine/anthropic-engine.js +8 -0
  430. package/dist/agent/engine/registry.js +24 -19
  431. package/dist/agent/engine/translate-anthropic.js +16 -1
  432. package/dist/automations/service.d.ts +2 -0
  433. package/dist/automations/service.js +5 -1
  434. package/dist/chat-threads/schema.d.ts +2 -2
  435. package/dist/cli/changelog.js +21 -4
  436. package/dist/cli/create.d.ts +12 -1
  437. package/dist/cli/create.js +99 -11
  438. package/dist/cli/index.js +6 -6
  439. package/dist/cli/process.d.ts +7 -0
  440. package/dist/cli/process.js +12 -0
  441. package/dist/cli/workspace-skill-policy.d.ts +15 -0
  442. package/dist/cli/workspace-skill-policy.js +62 -0
  443. package/dist/cli/workspacify.js +36 -0
  444. package/dist/client/AgentPanel.d.ts +14 -2
  445. package/dist/client/AgentPanel.js +40 -24
  446. package/dist/client/AssistantChat.js +90 -182
  447. package/dist/client/FeedbackButton.js +15 -2
  448. package/dist/client/RouteTransitionIndicator.d.ts +14 -2
  449. package/dist/client/RouteTransitionIndicator.js +16 -5
  450. package/dist/client/agent-page/AgentTabsPage.js +19 -6
  451. package/dist/client/app-config.d.ts +3 -0
  452. package/dist/client/app-config.js +6 -0
  453. package/dist/client/app-providers.js +2 -1
  454. package/dist/client/changelog/Changelog.d.ts +2 -1
  455. package/dist/client/changelog/Changelog.js +7 -5
  456. package/dist/client/chat/markdown-renderer.d.ts +3 -1
  457. package/dist/client/chat/markdown-renderer.js +24 -24
  458. package/dist/client/chat/message-components.d.ts +23 -0
  459. package/dist/client/chat/message-components.js +27 -44
  460. package/dist/client/chat/tool-call-display.d.ts +2 -2
  461. package/dist/client/chat/tool-call-display.js +6 -3
  462. package/dist/client/chat-first/app-open-actions.d.ts +28 -0
  463. package/dist/client/chat-first/app-open-actions.js +25 -0
  464. package/dist/client/chat-first/index.d.ts +1 -0
  465. package/dist/client/chat-first/index.js +1 -0
  466. package/dist/client/chat-first/primary-nav.d.ts +2 -1
  467. package/dist/client/chat-first/primary-nav.js +12 -5
  468. package/dist/client/conversation/use-near-bottom-autoscroll.d.ts +14 -0
  469. package/dist/client/conversation/use-near-bottom-autoscroll.js +14 -0
  470. package/dist/client/error-format.js +32 -3
  471. package/dist/client/extensions/EmbeddedExtension.js +1 -0
  472. package/dist/client/extensions/ExtensionViewer.js +3 -1
  473. package/dist/client/extensions/InlineExtensionFrame.js +1 -0
  474. package/dist/client/extensions/iframe-bridge.d.ts +3 -2
  475. package/dist/client/extensions/iframe-bridge.js +4 -2
  476. package/dist/client/i18n.js +28 -5
  477. package/dist/client/integrations/IntegrationConnectionChoice.d.ts +11 -0
  478. package/dist/client/integrations/IntegrationConnectionChoice.js +6 -0
  479. package/dist/client/integrations/IntegrationGrid.d.ts +23 -0
  480. package/dist/client/integrations/IntegrationGrid.js +12 -0
  481. package/dist/client/integrations/IntegrationsPanel.js +92 -26
  482. package/dist/client/integrations/index.d.ts +2 -0
  483. package/dist/client/integrations/index.js +2 -0
  484. package/dist/client/onboarding/FirstRunOnboarding.d.ts +3 -1
  485. package/dist/client/onboarding/FirstRunOnboarding.js +26 -16
  486. package/dist/client/onboarding/first-run-enabled.js +1 -5
  487. package/dist/client/onboarding/first-run-startup-gate.d.ts +5 -0
  488. package/dist/client/onboarding/first-run-startup-gate.js +51 -0
  489. package/dist/client/onboarding/first-run-status.d.ts +7 -0
  490. package/dist/client/onboarding/first-run-status.js +26 -0
  491. package/dist/client/onboarding/index.d.ts +3 -0
  492. package/dist/client/onboarding/index.js +3 -0
  493. package/dist/client/onboarding/use-onboarding.d.ts +1 -0
  494. package/dist/client/onboarding/use-onboarding.js +13 -11
  495. package/dist/client/org/TeamPage.js +174 -28
  496. package/dist/client/resources/McpConnectionSuggestion.js +29 -72
  497. package/dist/client/resources/McpIntegrationDialog.d.ts +3 -1
  498. package/dist/client/resources/McpIntegrationDialog.js +176 -51
  499. package/dist/client/resources/index.d.ts +1 -0
  500. package/dist/client/resources/index.js +1 -0
  501. package/dist/client/resources/mcp-integration-catalog.d.ts +8 -0
  502. package/dist/client/resources/mcp-integration-catalog.js +28 -0
  503. package/dist/client/settings/ExtensionsSettingsContent.js +2 -2
  504. package/dist/client/settings/SettingsPanel.d.ts +6 -0
  505. package/dist/client/settings/SettingsPanel.js +21 -8
  506. package/dist/client/settings/SettingsRow.d.ts +3 -1
  507. package/dist/client/settings/SettingsRow.js +4 -2
  508. package/dist/client/settings/SettingsSkeleton.js +5 -3
  509. package/dist/client/settings/SettingsTabsPage.js +2 -2
  510. package/dist/client/settings/UsageSection.d.ts +4 -1
  511. package/dist/client/settings/UsageSection.js +187 -93
  512. package/dist/client/settings/agent-settings-search.js +6 -2
  513. package/dist/client/setup-connections/useBuilderConnectCardController.d.ts +1 -1
  514. package/dist/client/setup-connections/useBuilderConnectCardController.js +2 -2
  515. package/dist/client/sharing/ShareButton.d.ts +2 -0
  516. package/dist/client/sharing/ShareButton.js +9 -2
  517. package/dist/client/sharing/ShareDialog.js +4 -1
  518. package/dist/client/sharing/useShareButtonController.d.ts +2 -1
  519. package/dist/client/sharing/useShareButtonController.js +8 -0
  520. package/dist/client/sharing/useShareDialogController.d.ts +1 -1
  521. package/dist/client/sharing/useShareDialogController.js +2 -1
  522. package/dist/collab/struct-routes.d.ts +1 -1
  523. package/dist/config.d.ts +13 -0
  524. package/dist/config.js +53 -0
  525. package/dist/dashboard-storage/schema.d.ts +2 -2
  526. package/dist/data-programs/schema.d.ts +2 -2
  527. package/dist/extensions/actions.js +2 -1
  528. package/dist/extensions/html-shell.d.ts +2 -2
  529. package/dist/extensions/routes.js +4 -1
  530. package/dist/extensions/schema.d.ts +2 -2
  531. package/dist/file-upload/pre-upload-attachments.js +1 -1
  532. package/dist/framework-tools.js +3 -0
  533. package/dist/history/types.d.ts +1 -1
  534. package/dist/index.d.ts +2 -1
  535. package/dist/index.js +2 -1
  536. package/dist/jobs/background-automation-runner.d.ts +1 -0
  537. package/dist/jobs/background-automation-runner.js +4 -2
  538. package/dist/jobs/frontmatter.d.ts +4 -0
  539. package/dist/jobs/frontmatter.js +16 -0
  540. package/dist/jobs/run-history.js +1 -1
  541. package/dist/jobs/tools.js +9 -5
  542. package/dist/localization/default-messages.d.ts +27 -0
  543. package/dist/localization/default-messages.js +28 -1
  544. package/dist/notifications/routes.d.ts +5 -5
  545. package/dist/observability/routes.d.ts +3 -3
  546. package/dist/onboarding/plugin.js +17 -2
  547. package/dist/org/context.js +16 -0
  548. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  549. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  550. package/dist/review/actions/create-review-comment.js +1 -1
  551. package/dist/review/actions/delete-review-comment.js +4 -1
  552. package/dist/review/actions/list-review-comments.js +4 -2
  553. package/dist/review/actions/reply-review-comment.js +1 -1
  554. package/dist/review/types.d.ts +1 -1
  555. package/dist/secrets/routes.d.ts +9 -9
  556. package/dist/server/action-discovery.js +29 -0
  557. package/dist/server/agent-chat/plugin-options.d.ts +7 -0
  558. package/dist/server/agent-chat-plugin.d.ts +2 -0
  559. package/dist/server/agent-chat-plugin.js +12 -4
  560. package/dist/server/builder-browser.js +31 -0
  561. package/dist/server/onboarding-html.js +1 -1
  562. package/dist/server/transcribe-voice.d.ts +1 -1
  563. package/dist/server/workspace-provider-oauth.js +38 -26
  564. package/dist/setup-connections/index.d.ts +1 -1
  565. package/dist/setup-connections/index.js +1 -1
  566. package/dist/shared/first-run-onboarding.d.ts +6 -0
  567. package/dist/shared/first-run-onboarding.js +6 -0
  568. package/dist/sharing/access.d.ts +1 -1
  569. package/dist/sharing/access.js +4 -1
  570. package/dist/sharing/actions/share-resource.d.ts +1 -1
  571. package/dist/sharing/actions/share-resource.js +2 -2
  572. package/dist/sharing/registry.d.ts +2 -2
  573. package/dist/sharing/schema.d.ts +5 -5
  574. package/dist/sharing/schema.js +9 -6
  575. package/dist/styles/agent-native.css +21 -0
  576. package/dist/styles/chat-history-list.css +1 -1
  577. package/dist/templates/chat/.agents/skills/adding-a-feature/SKILL.md +3 -2
  578. package/dist/templates/chat/.agents/skills/context-awareness/SKILL.md +206 -0
  579. package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +73 -80
  580. package/dist/templates/chat/.agents/skills/portability/SKILL.md +107 -0
  581. package/dist/templates/chat/.agents/skills/secrets/SKILL.md +339 -0
  582. package/dist/templates/chat/.agents/skills/self-modifying-code/SKILL.md +3 -3
  583. package/dist/templates/chat/.agents/skills/shadcn-ui/SKILL.md +1 -1
  584. package/dist/templates/chat/.agents/skills/sharing/SKILL.md +1 -0
  585. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +116 -44
  586. package/dist/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  587. package/dist/templates/chat/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  588. package/dist/templates/chat/.agents/skills/workspace-conventions/SKILL.md +93 -0
  589. package/dist/templates/chat/AGENTS.md +4 -5
  590. package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +3 -2
  591. package/dist/templates/default/.agents/skills/context-awareness/SKILL.md +206 -0
  592. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +73 -80
  593. package/dist/templates/default/.agents/skills/portability/SKILL.md +107 -0
  594. package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +3 -3
  595. package/dist/templates/default/.agents/skills/shadcn-ui/SKILL.md +1 -1
  596. package/dist/templates/default/.agents/skills/sharing/SKILL.md +233 -0
  597. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +116 -44
  598. package/dist/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  599. package/dist/templates/default/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  600. package/dist/templates/default/.agents/skills/workspace-conventions/SKILL.md +93 -0
  601. package/dist/templates/default/AGENTS.md +17 -2
  602. package/dist/templates/default/agent-native.config.ts +6 -0
  603. package/dist/templates/default/app/i18n/ar-SA.ts +2 -0
  604. package/dist/templates/default/app/i18n/de-DE.ts +2 -0
  605. package/dist/templates/default/app/i18n/en-US.ts +3 -1
  606. package/dist/templates/default/app/i18n/es-ES.ts +2 -0
  607. package/dist/templates/default/app/i18n/fr-FR.ts +2 -0
  608. package/dist/templates/default/app/i18n/hi-IN.ts +2 -0
  609. package/dist/templates/default/app/i18n/ja-JP.ts +2 -0
  610. package/dist/templates/default/app/i18n/ko-KR.ts +2 -0
  611. package/dist/templates/default/app/i18n/pt-BR.ts +2 -0
  612. package/dist/templates/default/app/i18n/zh-CN.ts +2 -0
  613. package/dist/templates/default/app/i18n/zh-TW.ts +2 -0
  614. package/dist/templates/headless/.agents/skills/delegate-to-agent/SKILL.md +289 -0
  615. package/dist/templates/headless/.agents/skills/security/SKILL.md +324 -0
  616. package/dist/templates/headless/.agents/skills/storing-data/SKILL.md +178 -0
  617. package/dist/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +3 -2
  618. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +73 -80
  619. package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +3 -3
  620. package/dist/templates/workspace-core/.agents/skills/shadcn-ui/SKILL.md +1 -1
  621. package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +1 -0
  622. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +116 -44
  623. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  624. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  625. package/dist/templates/workspace-core/.agents/skills/workspace-conventions/SKILL.md +3 -2
  626. package/dist/templates/workspace-core/AGENTS.md +9 -3
  627. package/dist/templates/workspace-root/AGENTS.md +26 -1
  628. package/dist/templates/workspace-root/agent-native.config.ts +8 -0
  629. package/dist/triggers/actions.js +2 -2
  630. package/dist/usage/actions/get-usage-alerts.d.ts +5 -0
  631. package/dist/usage/actions/get-usage-alerts.js +16 -0
  632. package/dist/usage/actions/get-usage-metrics.d.ts +7 -0
  633. package/dist/usage/actions/get-usage-metrics.js +26 -0
  634. package/dist/usage/actions/manage-usage-alert.d.ts +14 -0
  635. package/dist/usage/actions/manage-usage-alert.js +48 -0
  636. package/dist/usage/alerts-store.d.ts +78 -0
  637. package/dist/usage/alerts-store.js +592 -0
  638. package/dist/usage/metrics-store.d.ts +95 -0
  639. package/dist/usage/metrics-store.js +386 -0
  640. package/dist/usage/store.js +10 -0
  641. package/dist/vite/action-types-plugin.js +28 -0
  642. package/dist/vite/agent-native-config-loader.d.ts +6 -0
  643. package/dist/vite/agent-native-config-loader.js +54 -2
  644. package/dist/vite/client.js +11 -5
  645. package/dist/workspace-connections/actions/bulk-update-workspace-user-groups.d.ts +6 -0
  646. package/dist/workspace-connections/actions/bulk-update-workspace-user-groups.js +24 -0
  647. package/dist/workspace-connections/actions/delete-workspace-user-group.d.ts +7 -0
  648. package/dist/workspace-connections/actions/delete-workspace-user-group.js +29 -0
  649. package/dist/workspace-connections/actions/list-workspace-user-groups.d.ts +2 -0
  650. package/dist/workspace-connections/actions/list-workspace-user-groups.js +13 -0
  651. package/dist/workspace-connections/actions/upsert-workspace-user-group.d.ts +6 -0
  652. package/dist/workspace-connections/actions/upsert-workspace-user-group.js +22 -0
  653. package/dist/workspace-connections/credential-key-aliases.d.ts +3 -0
  654. package/dist/workspace-connections/credential-key-aliases.js +31 -0
  655. package/dist/workspace-connections/credentials.js +1 -25
  656. package/dist/workspace-connections/groups.d.ts +32 -0
  657. package/dist/workspace-connections/groups.js +288 -0
  658. package/dist/workspace-connections/index.d.ts +3 -1
  659. package/dist/workspace-connections/index.js +3 -1
  660. package/dist/workspace-connections/store.d.ts +9 -0
  661. package/dist/workspace-connections/store.js +68 -8
  662. package/docs/content/components.mdx +2 -2
  663. package/docs/content/sharing.mdx +2 -1
  664. package/package.json +10 -2
  665. package/src/templates/chat/.agents/skills/adding-a-feature/SKILL.md +3 -2
  666. package/src/templates/chat/.agents/skills/context-awareness/SKILL.md +206 -0
  667. package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +73 -80
  668. package/src/templates/chat/.agents/skills/portability/SKILL.md +107 -0
  669. package/src/templates/chat/.agents/skills/secrets/SKILL.md +339 -0
  670. package/src/templates/chat/.agents/skills/self-modifying-code/SKILL.md +3 -3
  671. package/src/templates/chat/.agents/skills/shadcn-ui/SKILL.md +1 -1
  672. package/src/templates/chat/.agents/skills/sharing/SKILL.md +1 -0
  673. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +116 -44
  674. package/src/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  675. package/src/templates/chat/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  676. package/src/templates/chat/.agents/skills/workspace-conventions/SKILL.md +93 -0
  677. package/src/templates/chat/AGENTS.md +4 -5
  678. package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +3 -2
  679. package/src/templates/default/.agents/skills/context-awareness/SKILL.md +206 -0
  680. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +73 -80
  681. package/src/templates/default/.agents/skills/portability/SKILL.md +107 -0
  682. package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +3 -3
  683. package/src/templates/default/.agents/skills/shadcn-ui/SKILL.md +1 -1
  684. package/src/templates/default/.agents/skills/sharing/SKILL.md +233 -0
  685. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +116 -44
  686. package/src/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  687. package/src/templates/default/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  688. package/src/templates/default/.agents/skills/workspace-conventions/SKILL.md +93 -0
  689. package/src/templates/default/AGENTS.md +17 -2
  690. package/src/templates/default/agent-native.config.ts +6 -0
  691. package/src/templates/default/app/i18n/ar-SA.ts +2 -0
  692. package/src/templates/default/app/i18n/de-DE.ts +2 -0
  693. package/src/templates/default/app/i18n/en-US.ts +3 -1
  694. package/src/templates/default/app/i18n/es-ES.ts +2 -0
  695. package/src/templates/default/app/i18n/fr-FR.ts +2 -0
  696. package/src/templates/default/app/i18n/hi-IN.ts +2 -0
  697. package/src/templates/default/app/i18n/ja-JP.ts +2 -0
  698. package/src/templates/default/app/i18n/ko-KR.ts +2 -0
  699. package/src/templates/default/app/i18n/pt-BR.ts +2 -0
  700. package/src/templates/default/app/i18n/zh-CN.ts +2 -0
  701. package/src/templates/default/app/i18n/zh-TW.ts +2 -0
  702. package/src/templates/headless/.agents/skills/delegate-to-agent/SKILL.md +289 -0
  703. package/src/templates/headless/.agents/skills/security/SKILL.md +324 -0
  704. package/src/templates/headless/.agents/skills/storing-data/SKILL.md +178 -0
  705. package/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +3 -2
  706. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +73 -80
  707. package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +3 -3
  708. package/src/templates/workspace-core/.agents/skills/shadcn-ui/SKILL.md +1 -1
  709. package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +1 -0
  710. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +116 -44
  711. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +9 -3
  712. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/spreadsheet-source.md +148 -0
  713. package/src/templates/workspace-core/.agents/skills/workspace-conventions/SKILL.md +3 -2
  714. package/src/templates/workspace-core/AGENTS.md +9 -3
  715. package/src/templates/workspace-root/AGENTS.md +26 -1
  716. package/src/templates/workspace-root/agent-native.config.ts +8 -0
  717. package/corpus/templates/analytics/.agents/skills/capture-learnings/SKILL.md +0 -89
  718. package/corpus/templates/analytics/.agents/skills/create-skill/SKILL.md +0 -195
  719. package/corpus/templates/analytics/.agents/skills/feature-flags/SKILL.md +0 -169
  720. package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  721. package/corpus/templates/assets/.agents/skills/capture-learnings/SKILL.md +0 -89
  722. package/corpus/templates/assets/.agents/skills/create-skill/SKILL.md +0 -195
  723. package/corpus/templates/assets/.agents/skills/feature-flags/SKILL.md +0 -169
  724. package/corpus/templates/assets/.agents/skills/real-time-collab/SKILL.md +0 -392
  725. package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  726. package/corpus/templates/brain/.agents/skills/capture-learnings/SKILL.md +0 -89
  727. package/corpus/templates/brain/.agents/skills/create-skill/SKILL.md +0 -195
  728. package/corpus/templates/brain/.agents/skills/feature-flags/SKILL.md +0 -169
  729. package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  730. package/corpus/templates/calendar/.agents/skills/capture-learnings/SKILL.md +0 -89
  731. package/corpus/templates/calendar/.agents/skills/create-skill/SKILL.md +0 -195
  732. package/corpus/templates/calendar/.agents/skills/feature-flags/SKILL.md +0 -169
  733. package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  734. package/corpus/templates/chat/.agents/skills/capture-learnings/SKILL.md +0 -89
  735. package/corpus/templates/chat/.agents/skills/create-skill/SKILL.md +0 -195
  736. package/corpus/templates/chat/.agents/skills/feature-flags/SKILL.md +0 -169
  737. package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  738. package/corpus/templates/clips/.agents/skills/capture-learnings/SKILL.md +0 -89
  739. package/corpus/templates/clips/.agents/skills/create-skill/SKILL.md +0 -195
  740. package/corpus/templates/clips/.agents/skills/feature-flags/SKILL.md +0 -169
  741. package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  742. package/corpus/templates/content/.agents/skills/capture-learnings/SKILL.md +0 -89
  743. package/corpus/templates/content/.agents/skills/create-skill/SKILL.md +0 -195
  744. package/corpus/templates/content/.agents/skills/feature-flags/SKILL.md +0 -169
  745. package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  746. package/corpus/templates/crm/.agents/skills/feature-flags/SKILL.md +0 -169
  747. package/corpus/templates/crm/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  748. package/corpus/templates/design/.agents/skills/capture-learnings/SKILL.md +0 -89
  749. package/corpus/templates/design/.agents/skills/create-skill/SKILL.md +0 -195
  750. package/corpus/templates/design/.agents/skills/feature-flags/SKILL.md +0 -169
  751. package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  752. package/corpus/templates/dispatch/.agents/skills/capture-learnings/SKILL.md +0 -89
  753. package/corpus/templates/dispatch/.agents/skills/create-skill/SKILL.md +0 -195
  754. package/corpus/templates/dispatch/.agents/skills/feature-flags/SKILL.md +0 -169
  755. package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  756. package/corpus/templates/factory/.agents/skills/capture-learnings/SKILL.md +0 -89
  757. package/corpus/templates/factory/.agents/skills/create-skill/SKILL.md +0 -195
  758. package/corpus/templates/factory/.agents/skills/feature-flags/SKILL.md +0 -169
  759. package/corpus/templates/factory/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  760. package/corpus/templates/forms/.agents/skills/capture-learnings/SKILL.md +0 -89
  761. package/corpus/templates/forms/.agents/skills/create-skill/SKILL.md +0 -195
  762. package/corpus/templates/forms/.agents/skills/feature-flags/SKILL.md +0 -169
  763. package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  764. package/corpus/templates/macros/.agents/skills/feature-flags/SKILL.md +0 -169
  765. package/corpus/templates/macros/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  766. package/corpus/templates/mail/.agents/skills/capture-learnings/SKILL.md +0 -89
  767. package/corpus/templates/mail/.agents/skills/create-skill/SKILL.md +0 -195
  768. package/corpus/templates/mail/.agents/skills/feature-flags/SKILL.md +0 -169
  769. package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  770. package/corpus/templates/plan/.agents/skills/capture-learnings/SKILL.md +0 -89
  771. package/corpus/templates/plan/.agents/skills/create-skill/SKILL.md +0 -195
  772. package/corpus/templates/plan/.agents/skills/feature-flags/SKILL.md +0 -169
  773. package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  774. package/corpus/templates/slides/.agents/skills/capture-learnings/SKILL.md +0 -89
  775. package/corpus/templates/slides/.agents/skills/create-skill/SKILL.md +0 -195
  776. package/corpus/templates/slides/.agents/skills/feature-flags/SKILL.md +0 -169
  777. package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  778. package/corpus/templates/tasks/.agents/skills/capture-learnings/SKILL.md +0 -89
  779. package/corpus/templates/tasks/.agents/skills/create-skill/SKILL.md +0 -195
  780. package/corpus/templates/tasks/.agents/skills/feature-flags/SKILL.md +0 -169
  781. package/corpus/templates/tasks/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  782. package/dist/templates/chat/.agents/skills/capture-learnings/SKILL.md +0 -89
  783. package/dist/templates/chat/.agents/skills/create-skill/SKILL.md +0 -195
  784. package/dist/templates/chat/.agents/skills/feature-flags/SKILL.md +0 -169
  785. package/dist/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  786. package/dist/templates/default/.agents/skills/capture-learnings/SKILL.md +0 -89
  787. package/dist/templates/default/.agents/skills/create-skill/SKILL.md +0 -195
  788. package/dist/templates/default/.agents/skills/feature-flags/SKILL.md +0 -169
  789. package/dist/templates/default/.agents/skills/integration-webhooks/SKILL.md +0 -364
  790. package/dist/templates/default/.agents/skills/internationalization/SKILL.md +0 -82
  791. package/dist/templates/default/.agents/skills/onboarding/SKILL.md +0 -56
  792. package/dist/templates/default/.agents/skills/real-time-collab/SKILL.md +0 -392
  793. package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  794. package/dist/templates/headless/.agents/skills/feature-flags/SKILL.md +0 -169
  795. package/dist/templates/headless/.agents/skills/integration-webhooks/SKILL.md +0 -364
  796. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +0 -402
  797. package/dist/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +0 -4
  798. package/dist/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +0 -90
  799. package/dist/templates/headless/.agents/skills/turn-into-skill/SKILL.md +0 -112
  800. package/dist/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +0 -4
  801. package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  802. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +0 -411
  803. package/dist/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +0 -107
  804. package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +0 -176
  805. package/dist/templates/workspace-core/.agents/skills/agent-page/SKILL.md +0 -63
  806. package/dist/templates/workspace-core/.agents/skills/audit-log/SKILL.md +0 -96
  807. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +0 -295
  808. package/dist/templates/workspace-core/.agents/skills/automations/SKILL.md +0 -242
  809. package/dist/templates/workspace-core/.agents/skills/browser-sessions/SKILL.md +0 -131
  810. package/dist/templates/workspace-core/.agents/skills/capture-learnings/SKILL.md +0 -89
  811. package/dist/templates/workspace-core/.agents/skills/changelog/SKILL.md +0 -114
  812. package/dist/templates/workspace-core/.agents/skills/client-methods/SKILL.md +0 -115
  813. package/dist/templates/workspace-core/.agents/skills/client-methods/references/legacy-client-fetch-audit-2026-06-03.md +0 -62
  814. package/dist/templates/workspace-core/.agents/skills/client-side-routing/SKILL.md +0 -87
  815. package/dist/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +0 -139
  816. package/dist/templates/workspace-core/.agents/skills/context-xray/SKILL.md +0 -72
  817. package/dist/templates/workspace-core/.agents/skills/create-skill/SKILL.md +0 -195
  818. package/dist/templates/workspace-core/.agents/skills/data-programs/SKILL.md +0 -128
  819. package/dist/templates/workspace-core/.agents/skills/extension-points/SKILL.md +0 -219
  820. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +0 -533
  821. package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +0 -340
  822. package/dist/templates/workspace-core/.agents/skills/extensions/references/examples.md +0 -259
  823. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +0 -547
  824. package/dist/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +0 -157
  825. package/dist/templates/workspace-core/.agents/skills/feature-flags/SKILL.md +0 -169
  826. package/dist/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +0 -117
  827. package/dist/templates/workspace-core/.agents/skills/harness-agents/SKILL.md +0 -170
  828. package/dist/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +0 -364
  829. package/dist/templates/workspace-core/.agents/skills/internationalization/SKILL.md +0 -82
  830. package/dist/templates/workspace-core/.agents/skills/mvp-followup/SKILL.md +0 -52
  831. package/dist/templates/workspace-core/.agents/skills/native-navigation/SKILL.md +0 -67
  832. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -329
  833. package/dist/templates/workspace-core/.agents/skills/onboarding/SKILL.md +0 -56
  834. package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +0 -299
  835. package/dist/templates/workspace-core/.agents/skills/qa/SKILL.md +0 -333
  836. package/dist/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +0 -392
  837. package/dist/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +0 -74
  838. package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +0 -70
  839. package/dist/templates/workspace-core/.agents/skills/server-plugins/SKILL.md +0 -84
  840. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +0 -278
  841. package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  842. package/dist/templates/workspace-core/.agents/skills/visual-answer/SKILL.md +0 -101
  843. package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +0 -222
  844. package/dist/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +0 -363
  845. package/src/templates/chat/.agents/skills/capture-learnings/SKILL.md +0 -89
  846. package/src/templates/chat/.agents/skills/create-skill/SKILL.md +0 -195
  847. package/src/templates/chat/.agents/skills/feature-flags/SKILL.md +0 -169
  848. package/src/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  849. package/src/templates/default/.agents/skills/capture-learnings/SKILL.md +0 -89
  850. package/src/templates/default/.agents/skills/create-skill/SKILL.md +0 -195
  851. package/src/templates/default/.agents/skills/feature-flags/SKILL.md +0 -169
  852. package/src/templates/default/.agents/skills/integration-webhooks/SKILL.md +0 -364
  853. package/src/templates/default/.agents/skills/internationalization/SKILL.md +0 -82
  854. package/src/templates/default/.agents/skills/onboarding/SKILL.md +0 -56
  855. package/src/templates/default/.agents/skills/real-time-collab/SKILL.md +0 -392
  856. package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  857. package/src/templates/headless/.agents/skills/feature-flags/SKILL.md +0 -169
  858. package/src/templates/headless/.agents/skills/integration-webhooks/SKILL.md +0 -364
  859. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +0 -402
  860. package/src/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +0 -4
  861. package/src/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +0 -90
  862. package/src/templates/headless/.agents/skills/turn-into-skill/SKILL.md +0 -112
  863. package/src/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +0 -4
  864. package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  865. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +0 -411
  866. package/src/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +0 -107
  867. package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +0 -176
  868. package/src/templates/workspace-core/.agents/skills/agent-page/SKILL.md +0 -63
  869. package/src/templates/workspace-core/.agents/skills/audit-log/SKILL.md +0 -96
  870. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +0 -295
  871. package/src/templates/workspace-core/.agents/skills/automations/SKILL.md +0 -242
  872. package/src/templates/workspace-core/.agents/skills/browser-sessions/SKILL.md +0 -131
  873. package/src/templates/workspace-core/.agents/skills/capture-learnings/SKILL.md +0 -89
  874. package/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +0 -114
  875. package/src/templates/workspace-core/.agents/skills/client-methods/SKILL.md +0 -115
  876. package/src/templates/workspace-core/.agents/skills/client-methods/references/legacy-client-fetch-audit-2026-06-03.md +0 -62
  877. package/src/templates/workspace-core/.agents/skills/client-side-routing/SKILL.md +0 -87
  878. package/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +0 -139
  879. package/src/templates/workspace-core/.agents/skills/context-xray/SKILL.md +0 -72
  880. package/src/templates/workspace-core/.agents/skills/create-skill/SKILL.md +0 -195
  881. package/src/templates/workspace-core/.agents/skills/data-programs/SKILL.md +0 -128
  882. package/src/templates/workspace-core/.agents/skills/extension-points/SKILL.md +0 -219
  883. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +0 -533
  884. package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +0 -340
  885. package/src/templates/workspace-core/.agents/skills/extensions/references/examples.md +0 -259
  886. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +0 -547
  887. package/src/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +0 -157
  888. package/src/templates/workspace-core/.agents/skills/feature-flags/SKILL.md +0 -169
  889. package/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +0 -117
  890. package/src/templates/workspace-core/.agents/skills/harness-agents/SKILL.md +0 -170
  891. package/src/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +0 -364
  892. package/src/templates/workspace-core/.agents/skills/internationalization/SKILL.md +0 -82
  893. package/src/templates/workspace-core/.agents/skills/mvp-followup/SKILL.md +0 -52
  894. package/src/templates/workspace-core/.agents/skills/native-navigation/SKILL.md +0 -67
  895. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -329
  896. package/src/templates/workspace-core/.agents/skills/onboarding/SKILL.md +0 -56
  897. package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +0 -299
  898. package/src/templates/workspace-core/.agents/skills/qa/SKILL.md +0 -333
  899. package/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +0 -392
  900. package/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +0 -74
  901. package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +0 -70
  902. package/src/templates/workspace-core/.agents/skills/server-plugins/SKILL.md +0 -84
  903. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +0 -278
  904. package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +0 -127
  905. package/src/templates/workspace-core/.agents/skills/visual-answer/SKILL.md +0 -101
  906. package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +0 -222
  907. package/src/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +0 -363
@@ -4,6 +4,23 @@ import {
4
4
  useActionQuery,
5
5
  } from "@agent-native/core/client/hooks";
6
6
  import { useT } from "@agent-native/core/client/i18n";
7
+ import {
8
+ IntegrationConnectionChoice,
9
+ IntegrationGrid,
10
+ } from "@agent-native/core/client/integrations";
11
+ import { useAppRoles, useOrgRole } from "@agent-native/core/client/org";
12
+ import {
13
+ McpIntegrationDialog,
14
+ McpIntegrationLogo,
15
+ getDefaultMcpIntegrations,
16
+ useCreateMcpServer,
17
+ type DefaultMcpIntegration,
18
+ } from "@agent-native/core/client/resources";
19
+ import {
20
+ useShareOrgMemberSearch,
21
+ type ShareOrgMember,
22
+ } from "@agent-native/core/client/sharing";
23
+ import { credentialKeyMatches } from "@agent-native/core/workspace-connections/credential-key-aliases";
7
24
  import {
8
25
  ActionQueryError,
9
26
  DispatchShell,
@@ -31,10 +48,6 @@ import {
31
48
  } from "@agent-native/dispatch/components/ui/dialog";
32
49
  import { Input } from "@agent-native/dispatch/components/ui/input";
33
50
  import { Label } from "@agent-native/dispatch/components/ui/label";
34
- import {
35
- RadioGroup,
36
- RadioGroupItem,
37
- } from "@agent-native/dispatch/components/ui/radio-group";
38
51
  import {
39
52
  Select,
40
53
  SelectContent,
@@ -54,6 +67,7 @@ import {
54
67
  IconBuilding,
55
68
  IconChartBar,
56
69
  IconCheck,
70
+ IconChevronDown,
57
71
  IconCircleDashed,
58
72
  IconClock,
59
73
  IconDatabase,
@@ -63,6 +77,7 @@ import {
63
77
  IconPlugConnected,
64
78
  IconPlus,
65
79
  IconRefresh,
80
+ IconSearch,
66
81
  IconShieldCheck,
67
82
  IconTrash,
68
83
  IconUsersGroup,
@@ -91,6 +106,7 @@ const CONNECTION_QUERY_KEY = [
91
106
  "list-workspace-connections",
92
107
  CONNECTION_QUERY_PARAMS,
93
108
  ] as const;
109
+ const GROUP_QUERY_KEY = ["action", "list-workspace-user-groups", {}] as const;
94
110
 
95
111
  type IconComponent = ComponentType<{
96
112
  size?: number | string;
@@ -162,6 +178,8 @@ interface WorkspaceConnection {
162
178
  scopes: string[];
163
179
  config: Record<string, unknown>;
164
180
  allowedApps: string[];
181
+ allowedUsers?: string[];
182
+ allowedUserGroups?: string[];
165
183
  credentialRefs: WorkspaceConnectionCredentialRef[];
166
184
  createdAt: string;
167
185
  updatedAt: string;
@@ -259,6 +277,18 @@ interface ConnectionFormState {
259
277
  credentialRefs: WorkspaceConnectionCredentialRef[];
260
278
  allApps: boolean;
261
279
  selectedApps: string[];
280
+ allUsers: boolean;
281
+ selectedUsers: string[];
282
+ selectedUserGroups: string[];
283
+ }
284
+
285
+ interface WorkspaceUserGroup {
286
+ id: string;
287
+ orgId: string;
288
+ name: string;
289
+ memberEmails: string[];
290
+ createdAt: string;
291
+ updatedAt: string;
262
292
  }
263
293
 
264
294
  interface WorkspaceConnectionSetupPlanCredential {
@@ -300,6 +330,8 @@ interface WorkspaceConnectionSetupPlan {
300
330
  | "status"
301
331
  | "scopes"
302
332
  | "allowedApps"
333
+ | "allowedUsers"
334
+ | "allowedUserGroups"
303
335
  | "credentialRefs"
304
336
  | "lastError"
305
337
  > | null;
@@ -323,6 +355,14 @@ interface SetupWizardFormState {
323
355
  credentialRefs: WorkspaceConnectionCredentialRef[];
324
356
  grantMode: "all-apps" | "selected-apps";
325
357
  selectedApps: string[];
358
+ userGrantMode: "all-users" | "selected-users";
359
+ selectedUsers: string[];
360
+ selectedUserGroups: string[];
361
+ }
362
+
363
+ interface ProviderChoice {
364
+ provider: WorkspaceConnectionProvider;
365
+ personalIntegration: DefaultMcpIntegration;
326
366
  }
327
367
 
328
368
  const EMPTY_RESPONSE: WorkspaceConnectionsResponse = {
@@ -387,6 +427,17 @@ const PROVIDER_ICONS: Record<string, IconComponent> = {
387
427
  generic: IconWorld,
388
428
  };
389
429
 
430
+ const MCP_INTEGRATIONS_BY_ID = new Map(
431
+ getDefaultMcpIntegrations().map((integration) => [
432
+ integration.id,
433
+ integration,
434
+ ]),
435
+ );
436
+
437
+ const PROVIDER_LOGO_IDS: Record<string, string> = {
438
+ google_drive: "google-workspace",
439
+ };
440
+
390
441
  function cx(...classes: Array<string | false | null | undefined>) {
391
442
  return classes.filter(Boolean).join(" ");
392
443
  }
@@ -395,6 +446,25 @@ function iconForProvider(providerId: string): IconComponent {
395
446
  return PROVIDER_ICONS[providerId] ?? IconPlugConnected;
396
447
  }
397
448
 
449
+ function logoForProvider(providerId: string, providerName: string): ReactNode {
450
+ const integration = MCP_INTEGRATIONS_BY_ID.get(
451
+ PROVIDER_LOGO_IDS[providerId] ?? providerId,
452
+ );
453
+ if (!integration?.logoUrl) {
454
+ const Icon = iconForProvider(providerId);
455
+ return <Icon size={18} />;
456
+ }
457
+ return (
458
+ <McpIntegrationLogo
459
+ name={providerName}
460
+ logoUrl={integration.logoUrl}
461
+ integrationId={integration.id}
462
+ className="size-7 rounded-md border-0 bg-transparent"
463
+ imageClassName="size-full p-0.5"
464
+ />
465
+ );
466
+ }
467
+
398
468
  function iconForApp(appId: string): IconComponent {
399
469
  return APP_ICONS[appId] ?? IconUsersGroup;
400
470
  }
@@ -556,11 +626,13 @@ function missingRequiredCredentialKeys(
556
626
  refs: WorkspaceConnectionCredentialRef[],
557
627
  ): string[] {
558
628
  if (!provider) return [];
559
- const available = new Set(refs.map((ref) => ref.key.trim()).filter(Boolean));
560
629
  return provider.credentialKeys
561
630
  .filter((credential) => credential.required)
562
631
  .map((credential) => credential.key)
563
- .filter((key) => !available.has(key));
632
+ .filter(
633
+ (key) =>
634
+ !refs.some((ref) => credentialKeyMatches(provider.id, key, ref.key)),
635
+ );
564
636
  }
565
637
 
566
638
  function formFromConnection(
@@ -583,6 +655,9 @@ function formFromConnection(
583
655
  : [],
584
656
  allApps: connection.allowedApps.length === 0,
585
657
  selectedApps: connection.allowedApps,
658
+ allUsers: (connection.allowedUsers?.length ?? 0) === 0,
659
+ selectedUsers: connection.allowedUsers ?? [],
660
+ selectedUserGroups: connection.allowedUserGroups ?? [],
586
661
  };
587
662
  }
588
663
 
@@ -610,6 +685,14 @@ function setupFormFromPlan(
610
685
  : normalizeCredentialRefs(plan.suggestedCredentialRefs, plan.provider),
611
686
  grantMode: plan.grantRecommendation.accessMode,
612
687
  selectedApps,
688
+ userGrantMode:
689
+ connection &&
690
+ ((connection.allowedUsers?.length ?? 0) > 0 ||
691
+ (connection.allowedUserGroups?.length ?? 0) > 0)
692
+ ? "selected-users"
693
+ : "all-users",
694
+ selectedUsers: connection?.allowedUsers ?? [],
695
+ selectedUserGroups: connection?.allowedUserGroups ?? [],
613
696
  };
614
697
  }
615
698
 
@@ -629,32 +712,6 @@ function appIsGranted(
629
712
  );
630
713
  }
631
714
 
632
- function grantModeForApp(
633
- connection: WorkspaceConnection,
634
- appId: string,
635
- grants: WorkspaceConnectionsResponse["grants"],
636
- ): "all-apps" | "selected" | "explicit" | "off" {
637
- if (connection.allowedApps.length === 0) return "all-apps";
638
- if (connection.allowedApps.includes(appId)) return "selected";
639
- if (
640
- grants.some(
641
- (grant) =>
642
- grant.connectionId === connection.id &&
643
- (grant.appId === appId || grant.appId === "*"),
644
- )
645
- ) {
646
- return "explicit";
647
- }
648
- return "off";
649
- }
650
-
651
- function grantModeLabel(
652
- t: Translate,
653
- mode: ReturnType<typeof grantModeForApp>,
654
- ): string {
655
- return t(`integrations.grantMode.${mode}`);
656
- }
657
-
658
715
  function usageForAppGrant(
659
716
  connection: WorkspaceConnection,
660
717
  appId: string,
@@ -711,6 +768,38 @@ function summarizeGrant(
711
768
  return `${labels.join(", ")}${suffix}`;
712
769
  }
713
770
 
771
+ function summarizeUserAccess(
772
+ connection: WorkspaceConnection,
773
+ t: Translate,
774
+ groups: WorkspaceUserGroup[] = [],
775
+ ): string {
776
+ const allowedUsers = connection.allowedUsers ?? [];
777
+ const allowedGroupIds = connection.allowedUserGroups ?? [];
778
+ if (allowedUsers.length === 0 && allowedGroupIds.length === 0) {
779
+ return t(
780
+ /* i18n-key-ignore */
781
+ "integrations.allWorkspaceMembers",
782
+ { defaultValue: "All workspace members" },
783
+ );
784
+ }
785
+ const groupLabels = allowedGroupIds
786
+ .map((id) => groups.find((group) => group.id === id)?.name ?? id)
787
+ .slice(0, 2);
788
+ const labels = [
789
+ ...groupLabels,
790
+ ...(allowedUsers.length > 0 ? [`${allowedUsers.length} people`] : []),
791
+ ];
792
+ const suffix =
793
+ allowedGroupIds.length + (allowedUsers.length > 0 ? 1 : 0) > labels.length
794
+ ? ` +${
795
+ allowedGroupIds.length +
796
+ (allowedUsers.length > 0 ? 1 : 0) -
797
+ labels.length
798
+ }`
799
+ : "";
800
+ return `${labels.join(", ")}${suffix}`;
801
+ }
802
+
714
803
  function summarizeAppList(
715
804
  appIds: string[],
716
805
  grantApps: GrantApp[],
@@ -752,124 +841,32 @@ function providerHasOAuth(provider: WorkspaceConnectionProvider): boolean {
752
841
  return Boolean(provider.oauth) || provider.id === "slack";
753
842
  }
754
843
 
755
- function ProviderCard({
756
- provider,
757
- connections,
758
- onCreate,
759
- }: {
760
- provider: WorkspaceConnectionProvider;
761
- connections: WorkspaceConnection[];
762
- onCreate: () => void;
763
- }) {
764
- const t = useT();
765
- const Icon = iconForProvider(provider.id);
766
- const active = connections.filter((item) => item.status !== "disabled");
767
- const readiness = provider.readiness;
768
- const readinessStatus =
769
- readiness?.status ?? (active.length > 0 ? "ready" : "not_configured");
770
- return (
771
- <article className="flex min-h-[232px] flex-col justify-between rounded-lg border bg-card p-4 shadow-sm transition hover:border-foreground/20">
772
- <div className="space-y-3">
773
- <div className="flex items-start justify-between gap-3">
774
- <div className="flex h-10 w-10 items-center justify-center rounded-md border bg-muted">
775
- <Icon size={18} className="text-muted-foreground" />
776
- </div>
777
- <Pill className={READINESS_CLASSES[readinessStatus]}>
778
- {readinessStatus === "ready" ? (
779
- <IconCheck size={12} />
780
- ) : (
781
- <IconCircleDashed size={12} />
782
- )}
783
- {readinessLabel(t, readinessStatus)}
784
- </Pill>
785
- </div>
786
- <div>
787
- <h2 className="text-sm font-semibold text-foreground">
788
- {provider.label}
789
- </h2>
790
- <p className="mt-1 line-clamp-3 text-sm leading-5 text-muted-foreground">
791
- {provider.description}
792
- </p>
793
- </div>
794
- <div className="flex flex-wrap gap-1.5">
795
- {provider.capabilities.map((capability) => (
796
- <Pill key={capability} className="border-border bg-background">
797
- {capability}
798
- </Pill>
799
- ))}
800
- </div>
801
- {readiness?.missingRequiredCredentialKeys.length ? (
802
- <div className="rounded-md border border-amber-500/20 bg-amber-500/10 px-3 py-2 text-xs text-amber-700 dark:text-amber-400">
803
- {t("integrations.missingKeys", {
804
- keys: readiness.missingRequiredCredentialKeys
805
- .slice(0, 2)
806
- .join(", "),
807
- suffix:
808
- readiness.missingRequiredCredentialKeys.length > 2
809
- ? ` +${readiness.missingRequiredCredentialKeys.length - 2}`
810
- : "",
811
- })}
812
- </div>
813
- ) : null}
814
- </div>
815
- <div className="mt-4 flex items-center justify-between gap-3 border-t pt-3">
816
- <span className="text-xs text-muted-foreground">
817
- {active.length > 0
818
- ? t("integrations.activeConnection", { count: active.length })
819
- : provider.credentialKeys.length === 0
820
- ? t("integrations.noCredentialRefs")
821
- : t("integrations.credentialRefCount", {
822
- count: provider.credentialKeys.length,
823
- })}
824
- </span>
825
- <div className="flex items-center gap-2">
826
- {providerHasOAuth(provider) ? (
827
- <Button
828
- type="button"
829
- size="sm"
830
- onClick={() => startWorkspaceProviderOAuth(provider)}
831
- >
832
- <IconShieldCheck size={14} />
833
- {t("integrations.connect")}
834
- </Button>
835
- ) : null}
836
- <Button
837
- type="button"
838
- variant={providerHasOAuth(provider) ? "ghost" : "outline"}
839
- size="sm"
840
- onClick={onCreate}
841
- >
842
- <IconKey size={14} />
843
- {providerHasOAuth(provider)
844
- ? t("integrations.connectionFallback")
845
- : t("integrations.connect")}
846
- </Button>
847
- </div>
848
- </div>
849
- </article>
850
- );
851
- }
852
-
853
844
  function ConnectionRow({
854
845
  connection,
855
846
  provider,
856
847
  grantApps,
857
848
  grants,
849
+ groups,
858
850
  onEdit,
859
851
  onRepair,
860
852
  onDelete,
861
853
  onToggleGrant,
862
854
  grantPending,
855
+ canManage,
856
+ canManageGrant,
863
857
  }: {
864
858
  connection: WorkspaceConnection;
865
859
  provider?: WorkspaceConnectionProvider;
866
860
  grantApps: GrantApp[];
867
861
  grants: WorkspaceConnectionsResponse["grants"];
862
+ groups: WorkspaceUserGroup[];
868
863
  onEdit: () => void;
869
864
  onRepair: () => void;
870
865
  onDelete: () => void;
871
866
  onToggleGrant: (appId: string, granted: boolean) => void;
872
867
  grantPending: boolean;
868
+ canManage: boolean;
869
+ canManageGrant: (appId: string) => boolean;
873
870
  }) {
874
871
  const t = useT();
875
872
  const Icon = iconForProvider(connection.provider);
@@ -883,12 +880,12 @@ function ConnectionRow({
883
880
  connection.status === "needs_reauth" ||
884
881
  connection.status === "disabled";
885
882
  return (
886
- <article className="rounded-lg border bg-card shadow-sm">
883
+ <article className="rounded-lg bg-muted/35">
887
884
  <div className="dispatch-connection-card-grid grid gap-4 p-4">
888
885
  <div className="min-w-0 space-y-3">
889
886
  <div className="flex flex-wrap items-start justify-between gap-3">
890
887
  <div className="flex min-w-0 items-start gap-3">
891
- <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-md border bg-muted">
888
+ <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-background/80">
892
889
  <Icon size={18} className="text-muted-foreground" />
893
890
  </div>
894
891
  <div className="min-w-0">
@@ -914,59 +911,61 @@ function ConnectionRow({
914
911
  </p>
915
912
  </div>
916
913
  </div>
917
- <div className="flex items-center gap-1.5">
918
- {needsRepair ? (
914
+ {canManage ? (
915
+ <div className="flex items-center gap-1.5">
916
+ {needsRepair ? (
917
+ <Button
918
+ type="button"
919
+ variant="outline"
920
+ size="sm"
921
+ onClick={onRepair}
922
+ >
923
+ <IconRefresh size={14} />
924
+ {t("integrations.repair")}
925
+ </Button>
926
+ ) : null}
919
927
  <Button
920
928
  type="button"
921
- variant="outline"
929
+ variant="ghost"
922
930
  size="sm"
923
- onClick={onRepair}
931
+ onClick={onEdit}
924
932
  >
925
- <IconRefresh size={14} />
926
- {t("integrations.repair")}
933
+ <IconEdit size={14} />
934
+ {t("integrations.edit")}
927
935
  </Button>
928
- ) : null}
929
- <Button type="button" variant="ghost" size="sm" onClick={onEdit}>
930
- <IconEdit size={14} />
931
- {t("integrations.edit")}
932
- </Button>
933
- <Button
934
- type="button"
935
- variant="destructive"
936
- size="sm"
937
- onClick={onDelete}
938
- >
939
- <IconTrash size={14} />
940
- {t("integrations.delete")}
941
- </Button>
942
- </div>
936
+ <Button
937
+ type="button"
938
+ variant="ghost"
939
+ size="icon"
940
+ onClick={onDelete}
941
+ aria-label={`${t("integrations.delete")} ${connection.label}`}
942
+ title={`${t("integrations.delete")} ${connection.label}`}
943
+ className="text-muted-foreground hover:bg-destructive/10 hover:text-destructive"
944
+ >
945
+ <IconTrash size={14} />
946
+ </Button>
947
+ </div>
948
+ ) : null}
943
949
  </div>
944
950
 
945
- <div className="dispatch-connection-meta-grid grid gap-2">
946
- <ConnectionMeta
947
- icon={IconKey}
948
- label={t("integrations.credentialRefs")}
949
- value={
950
- connection.credentialRefs.length
951
- ? connection.credentialRefs.map((ref) => ref.key).join(", ")
952
- : t("integrations.none")
953
- }
954
- />
955
- <ConnectionMeta
956
- icon={IconShieldCheck}
957
- label={t("integrations.scopes")}
958
- value={
959
- connection.scopes.length
960
- ? connection.scopes.join(", ")
961
- : t("integrations.none")
962
- }
963
- />
951
+ <div className="dispatch-connection-meta-grid grid gap-x-6 gap-y-3">
952
+ {connection.scopes.length > 0 ? (
953
+ <ConnectionMeta
954
+ icon={IconShieldCheck}
955
+ label={t("integrations.scopes")}
956
+ value={connection.scopes.join(", ")}
957
+ />
958
+ ) : null}
964
959
  <ConnectionMeta
965
960
  icon={IconUsersGroup}
966
- label={t("integrations.access")}
967
- value={summarizeGrant(connection, grantApps, grants, t)}
961
+ label={t(
962
+ /* i18n-key-ignore */
963
+ "integrations.peopleWithAccess",
964
+ { defaultValue: "People with access" },
965
+ )}
966
+ value={summarizeUserAccess(connection, t, groups)}
968
967
  />
969
- {hasUsageTimestamp(connection) ? (
968
+ {connection.lastUsedAt ? (
970
969
  <ConnectionMeta
971
970
  icon={IconClock}
972
971
  label={t("integrations.usage")}
@@ -1000,20 +999,11 @@ function ConnectionRow({
1000
999
  ) : null}
1001
1000
  </div>
1002
1001
 
1003
- <div className="rounded-md border bg-background p-3">
1002
+ <div className="rounded-md bg-muted/20 p-3">
1004
1003
  <div className="mb-2 flex items-center justify-between gap-2">
1005
- <div>
1006
- <h3 className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
1007
- {t("integrations.appGrants")}
1008
- </h3>
1009
- <p className="mt-0.5 text-xs text-muted-foreground">
1010
- {connection.allowedApps.length === 0
1011
- ? t("integrations.everyWorkspaceAppCanReuse")
1012
- : t("integrations.selectedAppsCanReuse", {
1013
- apps: summarizeGrant(connection, grantApps, grants, t),
1014
- })}
1015
- </p>
1016
- </div>
1004
+ <h3 className="text-sm font-medium text-foreground">
1005
+ {t("integrations.appGrants")}
1006
+ </h3>
1017
1007
  <Pill className="border-border bg-muted text-muted-foreground">
1018
1008
  {connection.allowedApps.length === 0
1019
1009
  ? t("integrations.allApps")
@@ -1024,12 +1014,11 @@ function ConnectionRow({
1024
1014
  {grantApps.map((app) => {
1025
1015
  const AppIcon = app.icon;
1026
1016
  const granted = appIsGranted(connection, app.id, grants);
1027
- const mode = grantModeForApp(connection, app.id, grants);
1028
1017
  const usage = usageForAppGrant(connection, app.id, grants);
1029
1018
  return (
1030
1019
  <div
1031
1020
  key={app.id}
1032
- className="flex min-h-9 items-center justify-between gap-3 rounded-md border px-2.5 py-2"
1021
+ className="flex min-h-9 items-center justify-between gap-3 rounded-md bg-muted/40 px-2.5 py-2"
1033
1022
  >
1034
1023
  <div className="flex min-w-0 items-center gap-2">
1035
1024
  <AppIcon size={14} className="text-muted-foreground" />
@@ -1037,26 +1026,16 @@ function ConnectionRow({
1037
1026
  <span className="truncate text-sm font-medium">
1038
1027
  {app.label}
1039
1028
  </span>
1040
- {usage !== undefined ? (
1029
+ {usage != null ? (
1041
1030
  <span className="truncate text-xs text-muted-foreground">
1042
1031
  {formatTimestamp(t, usage)}
1043
1032
  </span>
1044
1033
  ) : null}
1045
1034
  </span>
1046
- <Pill
1047
- className={cx(
1048
- "h-5 px-1.5 text-[11px]",
1049
- granted
1050
- ? "border-green-500/20 bg-green-500/10 text-green-700 dark:text-green-400"
1051
- : "border-border bg-muted text-muted-foreground",
1052
- )}
1053
- >
1054
- {grantModeLabel(t, mode)}
1055
- </Pill>
1056
1035
  </div>
1057
1036
  <Switch
1058
1037
  checked={granted}
1059
- disabled={grantPending}
1038
+ disabled={grantPending || !canManageGrant(app.id)}
1060
1039
  onCheckedChange={(checked) =>
1061
1040
  onToggleGrant(app.id, checked)
1062
1041
  }
@@ -1088,12 +1067,12 @@ function ConnectionMeta({
1088
1067
  value: string;
1089
1068
  }) {
1090
1069
  return (
1091
- <div className="min-w-0 rounded-md border bg-background px-3 py-2">
1070
+ <div className="min-w-0">
1092
1071
  <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
1093
1072
  <Icon size={13} />
1094
1073
  {label}
1095
1074
  </div>
1096
- <div className="mt-1 truncate text-sm font-medium text-foreground">
1075
+ <div className="mt-0.5 truncate text-sm font-medium text-foreground">
1097
1076
  {value}
1098
1077
  </div>
1099
1078
  </div>
@@ -1152,17 +1131,27 @@ function Modal({
1152
1131
  open,
1153
1132
  onClose,
1154
1133
  children,
1134
+ fullscreen = false,
1155
1135
  }: {
1156
1136
  title: string;
1157
1137
  description?: string;
1158
1138
  open: boolean;
1159
1139
  onClose: () => void;
1160
1140
  children: ReactNode;
1141
+ fullscreen?: boolean;
1161
1142
  }) {
1162
1143
  return (
1163
1144
  <Dialog open={open} onOpenChange={(next) => !next && onClose()}>
1164
- <DialogContent className="max-h-[92vh] max-w-2xl overflow-y-auto p-0">
1165
- <DialogHeader className="border-b p-4 pe-10">
1145
+ <DialogContent
1146
+ aria-describedby={undefined}
1147
+ className={cx(
1148
+ "p-0",
1149
+ fullscreen
1150
+ ? "inset-0 flex h-[100dvh] max-h-none w-full max-w-none translate-x-0 translate-y-0 flex-col overflow-hidden rounded-none"
1151
+ : "max-h-[92vh] max-w-2xl overflow-y-auto",
1152
+ )}
1153
+ >
1154
+ <DialogHeader className="shrink-0 border-b p-4 pe-10 sm:px-8">
1166
1155
  <DialogTitle className="text-base">{title}</DialogTitle>
1167
1156
  {description ? (
1168
1157
  <DialogDescription>{description}</DialogDescription>
@@ -1174,64 +1163,556 @@ function Modal({
1174
1163
  );
1175
1164
  }
1176
1165
 
1177
- function ConnectionForm({
1166
+ function UserAccessControl({
1167
+ allUsers,
1168
+ selectedUsers,
1169
+ selectedUserGroups,
1170
+ groups,
1171
+ canManageGroups,
1172
+ onChange,
1173
+ onManageGroups,
1174
+ onEditGroup,
1175
+ }: {
1176
+ allUsers: boolean;
1177
+ selectedUsers: string[];
1178
+ selectedUserGroups: string[];
1179
+ groups: WorkspaceUserGroup[];
1180
+ canManageGroups: boolean;
1181
+ onChange: (next: {
1182
+ allUsers: boolean;
1183
+ selectedUsers: string[];
1184
+ selectedUserGroups: string[];
1185
+ }) => void;
1186
+ onManageGroups: () => void;
1187
+ onEditGroup: (group: WorkspaceUserGroup) => void;
1188
+ }) {
1189
+ const t = useT();
1190
+ const [open, setOpen] = useState(false);
1191
+ const [query, setQuery] = useState("");
1192
+ const memberSearch = useShareOrgMemberSearch(query, open && !allUsers, {
1193
+ limit: 100,
1194
+ });
1195
+ const selectedSet = useMemo(
1196
+ () => new Set(selectedUsers.map((email) => email.toLowerCase())),
1197
+ [selectedUsers],
1198
+ );
1199
+ const selectedGroupSet = useMemo(
1200
+ () => new Set(selectedUserGroups),
1201
+ [selectedUserGroups],
1202
+ );
1203
+
1204
+ function toggleMember(member: ShareOrgMember, checked: boolean) {
1205
+ const email = member.email.toLowerCase();
1206
+ const next = checked
1207
+ ? Array.from(new Set([...selectedUsers, email]))
1208
+ : selectedUsers.filter((value) => value.toLowerCase() !== email);
1209
+ onChange({
1210
+ allUsers: false,
1211
+ selectedUsers: next,
1212
+ selectedUserGroups,
1213
+ });
1214
+ }
1215
+
1216
+ function toggleGroup(groupId: string, checked: boolean) {
1217
+ const next = checked
1218
+ ? Array.from(new Set([...selectedUserGroups, groupId]))
1219
+ : selectedUserGroups.filter((value) => value !== groupId);
1220
+ onChange({ allUsers: false, selectedUsers, selectedUserGroups: next });
1221
+ }
1222
+
1223
+ const selectedGroupNames = groups
1224
+ .filter((group) => selectedGroupSet.has(group.id))
1225
+ .map((group) => group.name);
1226
+
1227
+ const summary = allUsers
1228
+ ? t(
1229
+ /* i18n-key-ignore */
1230
+ "integrations.allWorkspaceMembers",
1231
+ { defaultValue: "All workspace members" },
1232
+ )
1233
+ : [
1234
+ ...selectedGroupNames,
1235
+ ...(selectedUsers.length > 0
1236
+ ? [
1237
+ t("integrations.selectedPeople", {
1238
+ count: selectedUsers.length,
1239
+ }),
1240
+ ]
1241
+ : []),
1242
+ ].join(", ") || t("integrations.choosePeopleOrGroups");
1243
+
1244
+ return (
1245
+ <details
1246
+ open={open}
1247
+ onToggle={(event) => setOpen(event.currentTarget.open)}
1248
+ className="group rounded-lg bg-muted/30"
1249
+ >
1250
+ <summary className="flex cursor-pointer list-none items-center justify-between gap-3 px-4 py-3 text-sm font-medium text-foreground marker:hidden">
1251
+ <span className="flex min-w-0 items-center gap-2">
1252
+ <IconUsersGroup className="size-4 shrink-0 text-muted-foreground" />
1253
+ <span className="truncate">
1254
+ {t(
1255
+ /* i18n-key-ignore */
1256
+ "integrations.peopleWithAccess",
1257
+ { defaultValue: "People with access" },
1258
+ )}
1259
+ </span>
1260
+ <span className="truncate text-xs font-normal text-muted-foreground">
1261
+ {summary}
1262
+ </span>
1263
+ </span>
1264
+ <IconChevronDown className="size-4 shrink-0 text-muted-foreground transition-transform group-open:rotate-180" />
1265
+ </summary>
1266
+ <div className="grid gap-3 px-4 pb-4">
1267
+ <div className="flex items-center justify-between gap-4 rounded-md bg-muted/40 px-3 py-3">
1268
+ <div className="min-w-0">
1269
+ <p className="text-sm font-medium text-foreground">
1270
+ {t(
1271
+ /* i18n-key-ignore */
1272
+ "integrations.allWorkspaceMembers",
1273
+ { defaultValue: "All workspace members" },
1274
+ )}
1275
+ </p>
1276
+ <p className="mt-0.5 text-xs leading-5 text-muted-foreground">
1277
+ {t(
1278
+ /* i18n-key-ignore */
1279
+ "integrations.allWorkspaceMembersDescription",
1280
+ {
1281
+ defaultValue: "Give access to everyone in this workspace.",
1282
+ },
1283
+ )}
1284
+ </p>
1285
+ </div>
1286
+ <Switch
1287
+ checked={allUsers}
1288
+ onCheckedChange={(checked) =>
1289
+ onChange({
1290
+ allUsers: checked,
1291
+ selectedUsers: checked ? [] : selectedUsers,
1292
+ selectedUserGroups: checked ? [] : selectedUserGroups,
1293
+ })
1294
+ }
1295
+ aria-label={t(
1296
+ /* i18n-key-ignore */
1297
+ "integrations.allWorkspaceMembersAria",
1298
+ { defaultValue: "Allow all workspace members" },
1299
+ )}
1300
+ />
1301
+ </div>
1302
+
1303
+ {!allUsers ? (
1304
+ <div className="grid gap-4">
1305
+ {canManageGroups ? (
1306
+ <div className="grid gap-2">
1307
+ <div className="flex items-center justify-between gap-3">
1308
+ <div>
1309
+ <p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
1310
+ {t("integrations.groups")}
1311
+ </p>
1312
+ <p className="mt-0.5 text-xs text-muted-foreground">
1313
+ {t("integrations.groupsDescription")}
1314
+ </p>
1315
+ </div>
1316
+ <Button
1317
+ type="button"
1318
+ variant="ghost"
1319
+ size="sm"
1320
+ onClick={onManageGroups}
1321
+ className="h-7 px-2 text-xs"
1322
+ >
1323
+ <IconPlus size={13} />
1324
+ {t("integrations.newGroup")}
1325
+ </Button>
1326
+ </div>
1327
+ {groups.length > 0 ? (
1328
+ <div className="grid gap-1">
1329
+ {groups.map((group) => {
1330
+ const checked = selectedGroupSet.has(group.id);
1331
+ return (
1332
+ <div
1333
+ key={group.id}
1334
+ className="flex items-center gap-1 rounded-md bg-background/60 px-1 py-1 transition-colors hover:bg-accent/30"
1335
+ >
1336
+ <Label
1337
+ htmlFor={`connection-group-${group.id}`}
1338
+ className="flex min-w-0 flex-1 cursor-pointer items-center justify-between gap-3 px-2 py-1"
1339
+ >
1340
+ <span className="flex min-w-0 items-center gap-2">
1341
+ <IconUsersGroup
1342
+ size={14}
1343
+ className="shrink-0 text-muted-foreground"
1344
+ />
1345
+ <span className="truncate text-sm font-medium">
1346
+ {group.name}
1347
+ </span>
1348
+ <Pill className="border-0 bg-muted px-1.5 text-[11px] text-muted-foreground">
1349
+ {group.memberEmails.length}
1350
+ </Pill>
1351
+ </span>
1352
+ <Checkbox
1353
+ id={`connection-group-${group.id}`}
1354
+ checked={checked}
1355
+ onCheckedChange={(value) =>
1356
+ toggleGroup(group.id, value === true)
1357
+ }
1358
+ aria-label={group.name}
1359
+ />
1360
+ </Label>
1361
+ <Button
1362
+ type="button"
1363
+ variant="ghost"
1364
+ size="icon"
1365
+ className="size-7 shrink-0"
1366
+ onClick={() => onEditGroup(group)}
1367
+ aria-label={t("integrations.editGroupAria", {
1368
+ name: group.name,
1369
+ })}
1370
+ >
1371
+ <IconEdit size={13} />
1372
+ </Button>
1373
+ </div>
1374
+ );
1375
+ })}
1376
+ </div>
1377
+ ) : (
1378
+ <Button
1379
+ type="button"
1380
+ variant="ghost"
1381
+ onClick={onManageGroups}
1382
+ className="h-auto justify-start rounded-md bg-background/60 px-3 py-2 text-xs text-muted-foreground"
1383
+ >
1384
+ <IconPlus size={13} />
1385
+ {t("integrations.createGroupCta")}
1386
+ </Button>
1387
+ )}
1388
+ </div>
1389
+ ) : null}
1390
+
1391
+ <div className="grid gap-2">
1392
+ <p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
1393
+ {t("integrations.people")}
1394
+ </p>
1395
+ <div className="relative">
1396
+ <IconSearch className="pointer-events-none absolute start-3 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
1397
+ <Input
1398
+ value={query}
1399
+ onChange={(event) => setQuery(event.target.value)}
1400
+ placeholder={t(
1401
+ /* i18n-key-ignore */
1402
+ "integrations.searchPeoplePlaceholder",
1403
+ { defaultValue: "Search people" },
1404
+ )}
1405
+ aria-label={t(
1406
+ /* i18n-key-ignore */
1407
+ "integrations.searchPeopleLabel",
1408
+ { defaultValue: "Search workspace members" },
1409
+ )}
1410
+ className="h-9 ps-9"
1411
+ />
1412
+ </div>
1413
+ {memberSearch.isLoading ? (
1414
+ <p className="px-1 text-xs text-muted-foreground">
1415
+ {t(
1416
+ /* i18n-key-ignore */
1417
+ "integrations.loadingPeople",
1418
+ { defaultValue: "Loading people..." },
1419
+ )}
1420
+ </p>
1421
+ ) : memberSearch.error ? (
1422
+ <p className="px-1 text-xs text-destructive">
1423
+ {t(
1424
+ /* i18n-key-ignore */
1425
+ "integrations.peopleLoadError",
1426
+ { defaultValue: "Could not load workspace members." },
1427
+ )}
1428
+ </p>
1429
+ ) : memberSearch.members.length > 0 ? (
1430
+ <div className="grid gap-1">
1431
+ {memberSearch.members.map((member) => {
1432
+ const checked = selectedSet.has(member.email.toLowerCase());
1433
+ return (
1434
+ <Label
1435
+ key={member.email}
1436
+ htmlFor={`connection-user-${member.email}`}
1437
+ className="flex cursor-pointer items-center justify-between gap-3 rounded-md bg-background/60 px-3 py-2 transition-colors hover:bg-accent/30"
1438
+ >
1439
+ <span className="min-w-0">
1440
+ <span className="block truncate text-sm font-medium">
1441
+ {member.name || member.email}
1442
+ </span>
1443
+ {member.name ? (
1444
+ <span className="block truncate text-xs text-muted-foreground">
1445
+ {member.email}
1446
+ </span>
1447
+ ) : null}
1448
+ </span>
1449
+ <Checkbox
1450
+ id={`connection-user-${member.email}`}
1451
+ checked={checked}
1452
+ onCheckedChange={(value) =>
1453
+ toggleMember(member, value === true)
1454
+ }
1455
+ aria-label={member.email}
1456
+ />
1457
+ </Label>
1458
+ );
1459
+ })}
1460
+ </div>
1461
+ ) : (
1462
+ <p className="px-1 text-xs text-muted-foreground">
1463
+ {t(
1464
+ /* i18n-key-ignore */
1465
+ "integrations.noPeopleFound",
1466
+ { defaultValue: "No workspace members found." },
1467
+ )}
1468
+ </p>
1469
+ )}
1470
+ {memberSearch.hasMore ? (
1471
+ <Button
1472
+ type="button"
1473
+ variant="ghost"
1474
+ size="sm"
1475
+ onClick={memberSearch.loadMore}
1476
+ disabled={memberSearch.isLoadingMore}
1477
+ className="w-fit text-xs"
1478
+ >
1479
+ {t(
1480
+ /* i18n-key-ignore */
1481
+ "integrations.loadMorePeople",
1482
+ { defaultValue: "Load more" },
1483
+ )}
1484
+ </Button>
1485
+ ) : null}
1486
+ </div>
1487
+ </div>
1488
+ ) : null}
1489
+ </div>
1490
+ </details>
1491
+ );
1492
+ }
1493
+
1494
+ function GroupEditor({
1178
1495
  open,
1179
- form,
1180
- providers,
1181
- grantApps,
1496
+ group,
1182
1497
  saving,
1183
- onChange,
1184
1498
  onClose,
1185
- onSubmit,
1499
+ onSave,
1186
1500
  }: {
1187
1501
  open: boolean;
1188
- form: ConnectionFormState | null;
1189
- providers: WorkspaceConnectionProvider[];
1190
- grantApps: GrantApp[];
1502
+ group: WorkspaceUserGroup | null;
1191
1503
  saving: boolean;
1192
- onChange: (form: ConnectionFormState) => void;
1193
1504
  onClose: () => void;
1194
- onSubmit: (event: FormEvent<HTMLFormElement>) => void;
1505
+ onSave: (name: string, memberEmails: string[]) => void;
1195
1506
  }) {
1196
1507
  const t = useT();
1197
- if (!form) return null;
1198
- const provider = providers.find((item) => item.id === form.provider);
1199
- const credentialRefs = normalizeCredentialRefs(form.credentialRefs, provider);
1200
- const missingCredentialRefs = missingRequiredCredentialKeys(
1201
- provider,
1202
- credentialRefs,
1508
+ const [name, setName] = useState("");
1509
+ const [members, setMembers] = useState<string[]>([]);
1510
+ const [query, setQuery] = useState("");
1511
+ const memberSearch = useShareOrgMemberSearch(query, open, { limit: 100 });
1512
+ const selectedSet = useMemo(
1513
+ () => new Set(members.map((email) => email.toLowerCase())),
1514
+ [members],
1203
1515
  );
1516
+
1517
+ useEffect(() => {
1518
+ if (!open) return;
1519
+ setName(group?.name ?? "");
1520
+ setMembers(group?.memberEmails ?? []);
1521
+ setQuery("");
1522
+ }, [group, open]);
1523
+
1524
+ function toggleMember(email: string, checked: boolean) {
1525
+ const normalized = email.toLowerCase();
1526
+ setMembers((current) =>
1527
+ checked
1528
+ ? Array.from(new Set([...current, normalized]))
1529
+ : current.filter((value) => value !== normalized),
1530
+ );
1531
+ }
1532
+
1204
1533
  return (
1205
1534
  <Modal
1206
1535
  open={open}
1207
1536
  onClose={onClose}
1208
- title={
1209
- form.id
1210
- ? t("integrations.editConnection")
1211
- : t("integrations.newConnection")
1212
- }
1213
- description={provider?.description}
1537
+ title={t(group ? "integrations.editGroup" : "integrations.createGroup")}
1538
+ description={t("integrations.groupDescription")}
1214
1539
  >
1215
- <form onSubmit={onSubmit}>
1216
- <div className="grid gap-4 p-4">
1217
- <div className="grid gap-3 sm:grid-cols-2">
1218
- <div className="grid gap-1.5 text-sm">
1219
- <Label>{t("integrations.provider")}</Label>
1220
- <Select
1221
- value={form.provider}
1222
- onValueChange={(value) => {
1223
- const nextProvider = providers.find(
1224
- (item) => item.id === value,
1225
- );
1226
- onChange({
1227
- ...form,
1228
- provider: value,
1229
- label: form.label || nextProvider?.label || value,
1230
- credentialRefs: nextProvider
1231
- ? credentialRefsForProvider(nextProvider)
1232
- : form.credentialRefs,
1233
- });
1234
- }}
1540
+ <form
1541
+ className="flex min-h-0 flex-1 flex-col"
1542
+ onSubmit={(event) => {
1543
+ event.preventDefault();
1544
+ onSave(name.trim(), members);
1545
+ }}
1546
+ >
1547
+ <div className="min-h-0 flex-1 space-y-5 overflow-y-auto p-5">
1548
+ <TextField
1549
+ label={t("integrations.groupName")}
1550
+ value={name}
1551
+ onChange={setName}
1552
+ placeholder={t("integrations.groupNamePlaceholder")}
1553
+ required
1554
+ />
1555
+
1556
+ <div className="grid gap-2">
1557
+ <div className="flex items-end justify-between gap-3">
1558
+ <div>
1559
+ <Label>{t("integrations.members")}</Label>
1560
+ <p className="mt-1 text-xs text-muted-foreground">
1561
+ {t("integrations.groupMembersDescription")}
1562
+ </p>
1563
+ </div>
1564
+ <Pill className="border-0 bg-muted text-muted-foreground">
1565
+ {t("integrations.selectedMembers", { count: members.length })}
1566
+ </Pill>
1567
+ </div>
1568
+ <div className="relative">
1569
+ <IconSearch className="pointer-events-none absolute start-3 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
1570
+ <Input
1571
+ value={query}
1572
+ onChange={(event) => setQuery(event.target.value)}
1573
+ placeholder={t("integrations.searchPeoplePlaceholder")}
1574
+ aria-label={t("integrations.searchPeopleLabel")}
1575
+ className="h-9 ps-9"
1576
+ />
1577
+ </div>
1578
+ {memberSearch.isLoading ? (
1579
+ <p className="px-1 text-xs text-muted-foreground">
1580
+ {t("integrations.loadingPeople")}
1581
+ </p>
1582
+ ) : memberSearch.error ? (
1583
+ <p className="px-1 text-xs text-destructive">
1584
+ {t("integrations.peopleLoadError")}
1585
+ </p>
1586
+ ) : (
1587
+ <div className="grid gap-1">
1588
+ {memberSearch.members.map((member) => {
1589
+ const checked = selectedSet.has(member.email.toLowerCase());
1590
+ return (
1591
+ <Label
1592
+ key={member.email}
1593
+ htmlFor={`group-member-${member.email}`}
1594
+ className="flex cursor-pointer items-center justify-between gap-3 rounded-md bg-muted/35 px-3 py-2 transition-colors hover:bg-muted/55"
1595
+ >
1596
+ <span className="min-w-0">
1597
+ <span className="block truncate text-sm font-medium">
1598
+ {member.name || member.email}
1599
+ </span>
1600
+ {member.name ? (
1601
+ <span className="block truncate text-xs text-muted-foreground">
1602
+ {member.email}
1603
+ </span>
1604
+ ) : null}
1605
+ </span>
1606
+ <Checkbox
1607
+ id={`group-member-${member.email}`}
1608
+ checked={checked}
1609
+ onCheckedChange={(value) =>
1610
+ toggleMember(member.email, value === true)
1611
+ }
1612
+ aria-label={member.email}
1613
+ />
1614
+ </Label>
1615
+ );
1616
+ })}
1617
+ </div>
1618
+ )}
1619
+ {memberSearch.hasMore ? (
1620
+ <Button
1621
+ type="button"
1622
+ variant="ghost"
1623
+ size="sm"
1624
+ onClick={memberSearch.loadMore}
1625
+ disabled={memberSearch.isLoadingMore}
1626
+ className="w-fit text-xs"
1627
+ >
1628
+ {t("integrations.loadMorePeople")}
1629
+ </Button>
1630
+ ) : null}
1631
+ </div>
1632
+ </div>
1633
+ <DialogFooter className="shrink-0 border-t p-4">
1634
+ <Button type="button" variant="ghost" onClick={onClose}>
1635
+ {t("integrations.cancel")}
1636
+ </Button>
1637
+ <Button type="submit" disabled={saving || !name.trim()}>
1638
+ {saving ? <IconRefresh size={14} className="animate-spin" /> : null}
1639
+ {t(group ? "integrations.saveGroup" : "integrations.createGroup")}
1640
+ </Button>
1641
+ </DialogFooter>
1642
+ </form>
1643
+ </Modal>
1644
+ );
1645
+ }
1646
+
1647
+ function ConnectionForm({
1648
+ open,
1649
+ form,
1650
+ providers,
1651
+ grantApps,
1652
+ groups,
1653
+ canManageGroups,
1654
+ canManageAllApps,
1655
+ saving,
1656
+ onChange,
1657
+ onClose,
1658
+ onSubmit,
1659
+ onManageGroups,
1660
+ onEditGroup,
1661
+ }: {
1662
+ open: boolean;
1663
+ form: ConnectionFormState | null;
1664
+ providers: WorkspaceConnectionProvider[];
1665
+ grantApps: GrantApp[];
1666
+ groups: WorkspaceUserGroup[];
1667
+ canManageGroups: boolean;
1668
+ canManageAllApps: boolean;
1669
+ saving: boolean;
1670
+ onChange: (form: ConnectionFormState) => void;
1671
+ onClose: () => void;
1672
+ onSubmit: (event: FormEvent<HTMLFormElement>) => void;
1673
+ onManageGroups: () => void;
1674
+ onEditGroup: (group: WorkspaceUserGroup) => void;
1675
+ }) {
1676
+ const t = useT();
1677
+ if (!form) return null;
1678
+ const provider = providers.find((item) => item.id === form.provider);
1679
+ const credentialRefs = normalizeCredentialRefs(form.credentialRefs, provider);
1680
+ const missingCredentialRefs = missingRequiredCredentialKeys(
1681
+ provider,
1682
+ credentialRefs,
1683
+ );
1684
+ return (
1685
+ <Modal
1686
+ open={open}
1687
+ onClose={onClose}
1688
+ fullscreen
1689
+ title={
1690
+ form.id
1691
+ ? t("integrations.editConnection")
1692
+ : t("integrations.newConnection")
1693
+ }
1694
+ description={provider?.description}
1695
+ >
1696
+ <form onSubmit={onSubmit}>
1697
+ <div className="grid gap-4 p-4">
1698
+ <div className="grid gap-3 sm:grid-cols-2">
1699
+ <div className="grid gap-1.5 text-sm">
1700
+ <Label>{t("integrations.provider")}</Label>
1701
+ <Select
1702
+ value={form.provider}
1703
+ onValueChange={(value) => {
1704
+ const nextProvider = providers.find(
1705
+ (item) => item.id === value,
1706
+ );
1707
+ onChange({
1708
+ ...form,
1709
+ provider: value,
1710
+ label: form.label || nextProvider?.label || value,
1711
+ credentialRefs: nextProvider
1712
+ ? credentialRefsForProvider(nextProvider)
1713
+ : form.credentialRefs,
1714
+ });
1715
+ }}
1235
1716
  >
1236
1717
  <SelectTrigger>
1237
1718
  <SelectValue placeholder={t("integrations.chooseProvider")} />
@@ -1300,27 +1781,43 @@ function ConnectionForm({
1300
1781
  />
1301
1782
  </div>
1302
1783
 
1303
- <div className="rounded-md border bg-background p-3">
1304
- <div className="flex items-center justify-between gap-3">
1784
+ <div className="rounded-md bg-muted/30 p-3">
1785
+ {canManageAllApps ? (
1786
+ <div className="flex items-center justify-between gap-3">
1787
+ <div>
1788
+ <div className="text-sm font-medium">
1789
+ {t("integrations.accessMode")}
1790
+ </div>
1791
+ <div className="text-xs text-muted-foreground">
1792
+ {form.allApps
1793
+ ? t("integrations.allAppsCanReuseConnection")
1794
+ : t("integrations.onlySelectedAppsCanReuseConnection")}
1795
+ </div>
1796
+ </div>
1797
+ <Switch
1798
+ checked={form.allApps}
1799
+ onCheckedChange={(checked) =>
1800
+ onChange({ ...form, allApps: checked })
1801
+ }
1802
+ aria-label={t("integrations.grantAllWorkspaceAppsAria")}
1803
+ />
1804
+ </div>
1805
+ ) : (
1305
1806
  <div>
1306
1807
  <div className="text-sm font-medium">
1307
- {t("integrations.accessMode")}
1808
+ {t("integrations.dispatchOnlyAccess", {
1809
+ defaultValue: "Dispatch only",
1810
+ })}
1308
1811
  </div>
1309
1812
  <div className="text-xs text-muted-foreground">
1310
- {form.allApps
1311
- ? t("integrations.allAppsCanReuseConnection")
1312
- : t("integrations.onlySelectedAppsCanReuseConnection")}
1813
+ {t("integrations.dispatchOnlyAccessDescription", {
1814
+ defaultValue:
1815
+ "You can manage connections for Dispatch. Organization admins can share them with other apps.",
1816
+ })}
1313
1817
  </div>
1314
1818
  </div>
1315
- <Switch
1316
- checked={form.allApps}
1317
- onCheckedChange={(checked) =>
1318
- onChange({ ...form, allApps: checked })
1319
- }
1320
- aria-label={t("integrations.grantAllWorkspaceAppsAria")}
1321
- />
1322
- </div>
1323
- {!form.allApps ? (
1819
+ )}
1820
+ {!form.allApps && canManageAllApps ? (
1324
1821
  <div className="mt-3 flex flex-wrap gap-2">
1325
1822
  {grantApps.map((app) => {
1326
1823
  const AppIcon = app.icon;
@@ -1354,6 +1851,24 @@ function ConnectionForm({
1354
1851
  ) : null}
1355
1852
  </div>
1356
1853
 
1854
+ <UserAccessControl
1855
+ allUsers={form.allUsers}
1856
+ selectedUsers={form.selectedUsers}
1857
+ selectedUserGroups={form.selectedUserGroups}
1858
+ groups={groups}
1859
+ canManageGroups={canManageGroups}
1860
+ onManageGroups={onManageGroups}
1861
+ onEditGroup={onEditGroup}
1862
+ onChange={({ allUsers, selectedUsers, selectedUserGroups }) =>
1863
+ onChange({
1864
+ ...form,
1865
+ allUsers,
1866
+ selectedUsers,
1867
+ selectedUserGroups,
1868
+ })
1869
+ }
1870
+ />
1871
+
1357
1872
  <CredentialRefsEditor
1358
1873
  provider={provider}
1359
1874
  refs={form.credentialRefs}
@@ -1410,14 +1925,47 @@ function CredentialRefsEditor({
1410
1925
  provider,
1411
1926
  refs,
1412
1927
  missingRefs,
1928
+ progressive = false,
1413
1929
  onChange,
1414
1930
  }: {
1415
1931
  provider?: WorkspaceConnectionProvider;
1416
1932
  refs: WorkspaceConnectionCredentialRef[];
1417
1933
  missingRefs: string[];
1934
+ progressive?: boolean;
1418
1935
  onChange: (refs: WorkspaceConnectionCredentialRef[]) => void;
1419
1936
  }) {
1420
1937
  const t = useT();
1938
+ if (
1939
+ progressive &&
1940
+ refs.length === 0 &&
1941
+ provider?.credentialKeys.length === 0
1942
+ ) {
1943
+ return (
1944
+ <div className="grid gap-5">
1945
+ <div>
1946
+ <h2 className="text-lg font-semibold tracking-[-0.02em] text-foreground">
1947
+ {t("integrations.credentialRefs")}
1948
+ </h2>
1949
+ <p className="mt-1 text-sm leading-6 text-muted-foreground">
1950
+ {t(
1951
+ /* i18n-key-ignore */
1952
+ "integrations.noCredentialRefs",
1953
+ { defaultValue: "No keys are needed for this connection." },
1954
+ )}
1955
+ </p>
1956
+ </div>
1957
+ <Button
1958
+ type="button"
1959
+ variant="outline"
1960
+ className="w-fit"
1961
+ onClick={() => onChange(appendCredentialRef([], provider))}
1962
+ >
1963
+ <IconPlus size={14} />
1964
+ {t("integrations.addRef")}
1965
+ </Button>
1966
+ </div>
1967
+ );
1968
+ }
1421
1969
  const providerKeys = new Map(
1422
1970
  provider?.credentialKeys.map((credential) => [
1423
1971
  credential.key,
@@ -1427,7 +1975,7 @@ function CredentialRefsEditor({
1427
1975
  const rows = refs.length > 0 ? refs : appendCredentialRef([], provider);
1428
1976
 
1429
1977
  return (
1430
- <div className="rounded-md border bg-background p-3">
1978
+ <div className="rounded-md bg-muted/30 p-3">
1431
1979
  <div className="flex flex-wrap items-start justify-between gap-3">
1432
1980
  <div>
1433
1981
  <div className="text-sm font-medium">
@@ -1454,7 +2002,7 @@ function CredentialRefsEditor({
1454
2002
  return (
1455
2003
  <div
1456
2004
  key={`${index}:${ref.provider ?? provider?.id ?? "provider"}`}
1457
- className="grid gap-2 rounded-md border px-3 py-3 sm:grid-cols-[minmax(0,1fr)_8rem_auto]"
2005
+ className="grid gap-2 rounded-md bg-muted/40 px-3 py-3 sm:grid-cols-[minmax(0,1fr)_8rem_auto]"
1458
2006
  >
1459
2007
  <div className="grid min-w-0 gap-1.5 text-sm">
1460
2008
  <Label className="flex items-center gap-1.5">
@@ -1561,13 +2109,17 @@ function SetupWizard({
1561
2109
  form,
1562
2110
  providers,
1563
2111
  grantApps,
2112
+ groups,
2113
+ canManageGroups,
2114
+ canManageAllApps,
1564
2115
  loading,
1565
2116
  saving,
1566
2117
  onStepChange,
1567
- onProviderChange,
1568
2118
  onChange,
1569
2119
  onClose,
1570
2120
  onSubmit,
2121
+ onManageGroups,
2122
+ onEditGroup,
1571
2123
  }: {
1572
2124
  open: boolean;
1573
2125
  state: SetupWizardState | null;
@@ -1576,13 +2128,17 @@ function SetupWizard({
1576
2128
  form: SetupWizardFormState | null;
1577
2129
  providers: WorkspaceConnectionProvider[];
1578
2130
  grantApps: GrantApp[];
2131
+ groups: WorkspaceUserGroup[];
2132
+ canManageGroups: boolean;
2133
+ canManageAllApps: boolean;
1579
2134
  loading: boolean;
1580
2135
  saving: boolean;
1581
2136
  onStepChange: (step: number) => void;
1582
- onProviderChange: (providerId: string) => void;
1583
2137
  onChange: (form: SetupWizardFormState) => void;
1584
2138
  onClose: () => void;
1585
2139
  onSubmit: () => void;
2140
+ onManageGroups: () => void;
2141
+ onEditGroup: (group: WorkspaceUserGroup) => void;
1586
2142
  }) {
1587
2143
  const t = useT();
1588
2144
  const provider =
@@ -1594,18 +2150,34 @@ function SetupWizard({
1594
2150
  provider,
1595
2151
  credentialRefs,
1596
2152
  );
2153
+ const visiblePlanWarnings = (plan?.warnings ?? []).filter(
2154
+ (warning) =>
2155
+ !warning.startsWith("Missing required credential refs:") ||
2156
+ missingCredentialRefs.length > 0,
2157
+ );
2158
+ const hasCredentialStep = Boolean(
2159
+ provider &&
2160
+ (provider.credentialKeys.length > 0 || credentialRefs.length > 0),
2161
+ );
1597
2162
  const selectedApps = form?.selectedApps ?? [];
2163
+ const selectedUsers = form?.selectedUsers ?? [];
2164
+ const selectedUserGroups = form?.selectedUserGroups ?? [];
1598
2165
  const stepItems = [
1599
- { label: t("integrations.provider"), icon: IconPlugConnected },
1600
- { label: t("integrations.refs"), icon: IconKey },
1601
- { label: t("integrations.access"), icon: IconShieldCheck },
2166
+ t("integrations.provider"),
2167
+ ...(hasCredentialStep ? [t("integrations.refs")] : []),
2168
+ t("integrations.access"),
1602
2169
  ];
1603
- const canAdvance =
1604
- step === 0
1605
- ? Boolean(form?.label.trim())
1606
- : step === 1
1607
- ? missingCredentialRefs.length === 0
1608
- : form?.grantMode === "all-apps" || selectedApps.length > 0;
2170
+ const isProviderStep = step === 0;
2171
+ const isCredentialStep = hasCredentialStep && step === 1;
2172
+ const isAccessStep = step === stepItems.length - 1;
2173
+ const canAdvance = isProviderStep
2174
+ ? Boolean(form?.label.trim())
2175
+ : isCredentialStep
2176
+ ? missingCredentialRefs.length === 0
2177
+ : (form?.grantMode === "all-apps" || selectedApps.length > 0) &&
2178
+ (form?.userGrantMode === "all-users" ||
2179
+ selectedUsers.length > 0 ||
2180
+ selectedUserGroups.length > 0);
1609
2181
  const suggestedGrantApps = useMemo(() => {
1610
2182
  const map = new Map<
1611
2183
  string,
@@ -1630,11 +2202,30 @@ function SetupWizard({
1630
2202
  }
1631
2203
  return Array.from(map.values());
1632
2204
  }, [grantApps, plan?.suggestedApps]);
2205
+ const availableGrantApps = canManageAllApps
2206
+ ? suggestedGrantApps
2207
+ : suggestedGrantApps.filter((app) => app.id.toLowerCase() === "dispatch");
2208
+ const selectedAppLabels = availableGrantApps
2209
+ .filter((app) => selectedApps.includes(app.id))
2210
+ .map((app) => app.label);
2211
+ const accessSummary =
2212
+ form?.grantMode === "all-apps"
2213
+ ? t("integrations.allApps")
2214
+ : t("integrations.selectedApps");
2215
+ const accessSummaryDescription =
2216
+ form?.grantMode === "all-apps"
2217
+ ? t("integrations.everyWorkspaceAppCanReuse")
2218
+ : selectedAppLabels.length > 0
2219
+ ? t("integrations.selectedAppsCanReuse", {
2220
+ apps: selectedAppLabels.join(", "),
2221
+ })
2222
+ : t("integrations.chooseOneApp");
1633
2223
 
1634
2224
  return (
1635
2225
  <Modal
1636
2226
  open={open}
1637
2227
  onClose={onClose}
2228
+ fullscreen
1638
2229
  title={
1639
2230
  state?.mode === "repair"
1640
2231
  ? t("integrations.repairConnectionTitle", {
@@ -1647,342 +2238,346 @@ function SetupWizard({
1647
2238
  provider: provider?.label || t("integrations.providerFallback"),
1648
2239
  })
1649
2240
  }
1650
- description={provider?.description}
2241
+ description={undefined}
1651
2242
  >
1652
- <div className="grid gap-4 p-4">
1653
- <div className="grid grid-cols-3 gap-2">
1654
- {stepItems.map((item, index) => {
1655
- const Icon = item.icon;
1656
- const active = step === index;
1657
- const complete = step > index;
1658
- return (
1659
- <button
1660
- key={item.label}
1661
- type="button"
1662
- onClick={() => onStepChange(index)}
1663
- className={cx(
1664
- "flex min-h-10 items-center justify-center gap-2 rounded-md border px-2 text-xs font-medium transition",
1665
- active
1666
- ? "border-primary bg-primary text-primary-foreground"
1667
- : complete
1668
- ? "border-border bg-muted text-foreground"
1669
- : "border-border bg-background text-muted-foreground",
1670
- )}
1671
- >
1672
- {complete ? <IconCheck size={14} /> : <Icon size={14} />}
1673
- <span className="truncate">{item.label}</span>
1674
- </button>
1675
- );
1676
- })}
1677
- </div>
1678
-
1679
- {loading ? (
1680
- <div className="rounded-lg border border-dashed px-6 py-10 text-center text-sm text-muted-foreground">
1681
- {t("integrations.loadingSetupPlan")}
2243
+ <div className="min-h-0 flex-1 overflow-y-auto">
2244
+ <div className="mx-auto grid w-full max-w-2xl gap-8 px-6 py-8 sm:px-10 sm:py-12">
2245
+ <div className="grid gap-2">
2246
+ <div className="flex items-center justify-between gap-4 text-xs text-muted-foreground">
2247
+ <span>
2248
+ Step {step + 1} of {stepItems.length}
2249
+ </span>
2250
+ <span className="font-medium text-foreground">
2251
+ {stepItems[step]}
2252
+ </span>
2253
+ </div>
2254
+ <div className="h-1 overflow-hidden rounded-full bg-muted">
2255
+ <div
2256
+ className="h-full rounded-full bg-primary transition-[width] duration-200 ease-[var(--ease-out-strong)]"
2257
+ style={{ width: `${((step + 1) / stepItems.length) * 100}%` }}
2258
+ />
2259
+ </div>
1682
2260
  </div>
1683
- ) : null}
1684
2261
 
1685
- {!loading && form && provider ? (
1686
- <>
1687
- {plan?.warnings.length ? (
1688
- <div className="grid gap-2">
1689
- {plan.warnings.map((warning) => (
1690
- <div
1691
- key={warning}
1692
- className="flex items-start gap-2 rounded-md border border-amber-500/20 bg-amber-500/10 px-3 py-2 text-sm text-amber-700 dark:text-amber-400"
1693
- >
1694
- <IconAlertTriangle size={15} className="mt-0.5 shrink-0" />
1695
- <span className="min-w-0 break-words">{warning}</span>
1696
- </div>
1697
- ))}
1698
- </div>
1699
- ) : null}
2262
+ {loading ? (
2263
+ <div className="rounded-lg bg-muted/30 px-6 py-10 text-center text-sm text-muted-foreground">
2264
+ {t("integrations.loadingSetupPlan")}
2265
+ </div>
2266
+ ) : null}
1700
2267
 
1701
- {step === 0 ? (
1702
- <div className="grid gap-4">
1703
- <div className="grid gap-3 sm:grid-cols-2">
1704
- <div className="grid gap-1.5 text-sm">
1705
- <Label>{t("integrations.provider")}</Label>
1706
- <Select
1707
- value={form.provider}
1708
- disabled={state?.mode === "repair"}
1709
- onValueChange={onProviderChange}
2268
+ {!loading && form && provider ? (
2269
+ <>
2270
+ {visiblePlanWarnings.length ? (
2271
+ <div className="grid gap-2">
2272
+ {visiblePlanWarnings.map((warning) => (
2273
+ <div
2274
+ key={warning}
2275
+ className="flex items-start gap-2 rounded-md border border-amber-500/20 bg-amber-500/10 px-3 py-2 text-sm text-amber-700 dark:text-amber-400"
1710
2276
  >
1711
- <SelectTrigger>
1712
- <SelectValue
1713
- placeholder={t("integrations.chooseProvider")}
1714
- />
1715
- </SelectTrigger>
1716
- <SelectContent>
1717
- {providers.map((item) => (
1718
- <SelectItem key={item.id} value={item.id}>
1719
- {item.label}
1720
- </SelectItem>
1721
- ))}
1722
- </SelectContent>
1723
- </Select>
2277
+ <IconAlertTriangle
2278
+ size={15}
2279
+ className="mt-0.5 shrink-0"
2280
+ />
2281
+ <span className="min-w-0 break-words">{warning}</span>
2282
+ </div>
2283
+ ))}
2284
+ </div>
2285
+ ) : null}
2286
+
2287
+ {isProviderStep ? (
2288
+ <div className="grid gap-6">
2289
+ <div className="flex items-center gap-3">
2290
+ <span className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted/40">
2291
+ {logoForProvider(provider.id, provider.label)}
2292
+ </span>
2293
+ <div className="min-w-0">
2294
+ <h2 className="text-lg font-semibold tracking-[-0.02em] text-foreground">
2295
+ {provider.label}
2296
+ </h2>
2297
+ <p className="mt-0.5 text-sm text-muted-foreground">
2298
+ {t("integrations.scopeWorkspace")}
2299
+ </p>
2300
+ </div>
1724
2301
  </div>
2302
+
1725
2303
  <TextField
1726
2304
  label={t("integrations.connectionLabel")}
1727
2305
  value={form.label}
1728
2306
  onChange={(value) => onChange({ ...form, label: value })}
1729
2307
  required
1730
2308
  />
1731
- </div>
1732
- <div className="grid gap-3 sm:grid-cols-2">
1733
- <TextField
1734
- label={t("integrations.accountLabel")}
1735
- value={form.accountLabel}
1736
- onChange={(value) =>
1737
- onChange({ ...form, accountLabel: value })
1738
- }
1739
- placeholder={t("integrations.accountLabelPlaceholder")}
1740
- />
1741
- <TextField
1742
- label={t("integrations.accountId")}
1743
- value={form.accountId}
1744
- onChange={(value) =>
1745
- onChange({ ...form, accountId: value })
1746
- }
1747
- placeholder={t("integrations.accountIdPlaceholder")}
1748
- />
1749
- </div>
1750
- <div className="rounded-md border bg-background p-3">
1751
- <div className="text-sm font-medium">
1752
- {t("integrations.providerPlan")}
1753
- </div>
1754
- <div className="mt-2 flex flex-wrap gap-1.5">
1755
- {provider.capabilities.map((capability) => (
1756
- <Pill key={capability} className="border-border bg-muted">
1757
- {capability}
1758
- </Pill>
1759
- ))}
1760
- </div>
1761
- <p className="mt-2 text-xs leading-5 text-muted-foreground">
1762
- {plan?.grantRecommendation.reason}
1763
- </p>
1764
- </div>
1765
- </div>
1766
- ) : null}
1767
2309
 
1768
- {step === 1 ? (
1769
- <CredentialRefsEditor
1770
- provider={provider}
1771
- refs={form.credentialRefs}
1772
- missingRefs={missingCredentialRefs}
1773
- onChange={(credentialRefs) =>
1774
- onChange({ ...form, credentialRefs })
1775
- }
1776
- />
1777
- ) : null}
2310
+ <details className="group rounded-lg bg-muted/30">
2311
+ <summary className="flex cursor-pointer list-none items-center justify-between gap-3 px-3.5 py-3 text-sm font-medium text-foreground marker:hidden">
2312
+ <span>
2313
+ {t(
2314
+ /* i18n-key-ignore */
2315
+ "integrations.advancedDetails",
2316
+ { defaultValue: "Advanced details" },
2317
+ )}
2318
+ </span>
2319
+ <IconChevronDown className="size-4 shrink-0 text-muted-foreground transition-transform group-open:rotate-180" />
2320
+ </summary>
2321
+ <div className="grid gap-3 px-3.5 pb-3.5 sm:grid-cols-2">
2322
+ <TextField
2323
+ label={t("integrations.accountLabel")}
2324
+ value={form.accountLabel}
2325
+ onChange={(value) =>
2326
+ onChange({ ...form, accountLabel: value })
2327
+ }
2328
+ placeholder={t("integrations.accountLabelPlaceholder")}
2329
+ />
2330
+ <TextField
2331
+ label={t("integrations.accountId")}
2332
+ value={form.accountId}
2333
+ onChange={(value) =>
2334
+ onChange({ ...form, accountId: value })
2335
+ }
2336
+ placeholder={t("integrations.accountIdPlaceholder")}
2337
+ />
2338
+ <div className="sm:col-span-2">
2339
+ <p className="text-xs font-medium text-foreground">
2340
+ {t("integrations.providerPlan")}
2341
+ </p>
2342
+ <div className="mt-2 flex flex-wrap gap-1.5">
2343
+ {provider.capabilities.map((capability) => (
2344
+ <Pill
2345
+ key={capability}
2346
+ className="border-border bg-muted"
2347
+ >
2348
+ {capability}
2349
+ </Pill>
2350
+ ))}
2351
+ </div>
2352
+ </div>
2353
+ </div>
2354
+ </details>
2355
+ </div>
2356
+ ) : null}
1778
2357
 
1779
- {step === 2 ? (
1780
- <div className="grid gap-4">
1781
- <GrantPreview
1782
- providerLabel={provider.label}
1783
- grantMode={form.grantMode}
1784
- selectedAppIds={selectedApps}
1785
- grantApps={suggestedGrantApps}
1786
- />
1787
- <RadioGroup
1788
- value={form.grantMode}
1789
- onValueChange={(value) =>
1790
- onChange({
1791
- ...form,
1792
- grantMode: value as SetupWizardFormState["grantMode"],
1793
- })
2358
+ {isCredentialStep ? (
2359
+ <CredentialRefsEditor
2360
+ provider={provider}
2361
+ refs={form.credentialRefs}
2362
+ missingRefs={missingCredentialRefs}
2363
+ progressive
2364
+ onChange={(credentialRefs) =>
2365
+ onChange({ ...form, credentialRefs })
1794
2366
  }
1795
- className="grid gap-2"
1796
- >
1797
- <Label
1798
- htmlFor="setup-grant-selected"
1799
- className="flex cursor-pointer items-start gap-3 rounded-md border bg-background p-3"
1800
- >
1801
- <RadioGroupItem
1802
- id="setup-grant-selected"
1803
- value="selected-apps"
1804
- className="mt-0.5"
1805
- />
1806
- <span className="grid gap-1">
1807
- <span className="text-sm font-medium">
1808
- {t("integrations.selectedApps")}
1809
- </span>
1810
- <span className="text-xs leading-5 text-muted-foreground">
1811
- {t("integrations.selectedAppsDescription")}
1812
- </span>
1813
- </span>
1814
- </Label>
1815
- <Label
1816
- htmlFor="setup-grant-all"
1817
- className="flex cursor-pointer items-start gap-3 rounded-md border bg-background p-3"
1818
- >
1819
- <RadioGroupItem
1820
- id="setup-grant-all"
1821
- value="all-apps"
1822
- className="mt-0.5"
1823
- />
1824
- <span className="grid gap-1">
1825
- <span className="text-sm font-medium">
1826
- {t("integrations.allApps")}
1827
- </span>
1828
- <span className="text-xs leading-5 text-muted-foreground">
1829
- {t("integrations.allAppsDescription")}
1830
- </span>
1831
- </span>
1832
- </Label>
1833
- </RadioGroup>
2367
+ />
2368
+ ) : null}
1834
2369
 
1835
- {form.grantMode === "selected-apps" ? (
1836
- <div className="rounded-md border bg-background p-3">
1837
- <div className="mb-3 text-sm font-medium">
1838
- {t("integrations.appGrants")}
2370
+ {isAccessStep ? (
2371
+ <div className="grid gap-5">
2372
+ <div>
2373
+ <h2 className="text-lg font-semibold tracking-[-0.02em] text-foreground">
2374
+ {t("integrations.access")}
2375
+ </h2>
2376
+ </div>
2377
+
2378
+ <div className="grid gap-2">
2379
+ <div className="flex items-center gap-3 rounded-lg bg-muted/30 px-4 py-3.5">
2380
+ <div className="min-w-0 flex-1">
2381
+ <p className="text-sm font-medium text-foreground">
2382
+ {accessSummary}
2383
+ </p>
2384
+ <p className="mt-0.5 text-xs leading-5 text-muted-foreground">
2385
+ {accessSummaryDescription}
2386
+ </p>
2387
+ </div>
1839
2388
  </div>
1840
- <div className="grid gap-2 sm:grid-cols-2">
1841
- {suggestedGrantApps.map((app) => {
1842
- const AppIcon = app.icon;
1843
- const selected = selectedApps.includes(app.id);
1844
- return (
1845
- <Label
1846
- key={app.id}
1847
- htmlFor={`setup-app-${app.id}`}
1848
- className="flex min-h-11 cursor-pointer items-center justify-between gap-3 rounded-md border px-3 py-2"
1849
- >
1850
- <span className="flex min-w-0 items-center gap-2">
1851
- <AppIcon
1852
- size={14}
1853
- className="text-muted-foreground"
1854
- />
1855
- <span className="truncate text-sm font-medium">
1856
- {app.label}
1857
- </span>
1858
- {app.recommended ? (
1859
- <Pill className="h-5 border-border bg-muted px-1.5 text-[11px]">
1860
- {t("integrations.suggested")}
1861
- </Pill>
1862
- ) : null}
1863
- </span>
1864
- <Checkbox
1865
- id={`setup-app-${app.id}`}
1866
- checked={selected}
2389
+
2390
+ {canManageAllApps ? (
2391
+ <details className="group rounded-lg bg-muted/30">
2392
+ <summary className="flex cursor-pointer list-none items-center justify-between gap-3 px-4 py-3 text-sm font-medium text-foreground marker:hidden">
2393
+ <span>{t("integrations.appGrants")}</span>
2394
+ <IconChevronDown className="size-4 shrink-0 text-muted-foreground transition-transform group-open:rotate-180" />
2395
+ </summary>
2396
+ <div className="grid gap-3 px-4 pb-4">
2397
+ <div className="flex items-center justify-between gap-4 rounded-md bg-muted/40 px-3 py-3">
2398
+ <div className="min-w-0">
2399
+ <p className="text-sm font-medium text-foreground">
2400
+ {t("integrations.allApps")}
2401
+ </p>
2402
+ <p className="mt-0.5 text-xs leading-5 text-muted-foreground">
2403
+ {t("integrations.allAppsDescription")}
2404
+ </p>
2405
+ </div>
2406
+ <Switch
2407
+ checked={form.grantMode === "all-apps"}
1867
2408
  onCheckedChange={(checked) =>
1868
2409
  onChange({
1869
2410
  ...form,
2411
+ grantMode: checked
2412
+ ? "all-apps"
2413
+ : "selected-apps",
1870
2414
  selectedApps: checked
1871
- ? Array.from(
1872
- new Set([...selectedApps, app.id]),
1873
- )
1874
- : selectedApps.filter(
1875
- (appId) => appId !== app.id,
1876
- ),
2415
+ ? []
2416
+ : selectedApps.length > 0
2417
+ ? selectedApps
2418
+ : suggestedGrantApps
2419
+ .filter((app) => app.recommended)
2420
+ .slice(0, 1)
2421
+ .map((app) => app.id),
1877
2422
  })
1878
2423
  }
1879
- aria-label={t("integrations.appGrantAria", {
1880
- action: selected
1881
- ? t("integrations.remove")
1882
- : t("integrations.grant"),
1883
- app: app.label,
1884
- })}
2424
+ aria-label={t(
2425
+ "integrations.grantAllWorkspaceAppsAria",
2426
+ )}
1885
2427
  />
1886
- </Label>
1887
- );
1888
- })}
1889
- </div>
1890
- {selectedApps.length === 0 ? (
1891
- <div className="mt-3 text-xs text-muted-foreground">
1892
- {t("integrations.chooseOneApp")}
1893
- </div>
2428
+ </div>
2429
+
2430
+ {form.grantMode === "selected-apps" ? (
2431
+ <div className="grid gap-3">
2432
+ <p className="text-xs text-muted-foreground">
2433
+ {t("integrations.chooseOneApp")}
2434
+ </p>
2435
+ <div className="grid gap-2 sm:grid-cols-2">
2436
+ {availableGrantApps.map((app) => {
2437
+ const AppIcon = app.icon;
2438
+ const selected = selectedApps.includes(
2439
+ app.id,
2440
+ );
2441
+ return (
2442
+ <Label
2443
+ key={app.id}
2444
+ htmlFor={`setup-app-${app.id}`}
2445
+ className="flex min-h-11 cursor-pointer items-center justify-between gap-3 rounded-lg bg-muted/40 px-3 py-2 transition-colors hover:bg-muted/60"
2446
+ >
2447
+ <span className="flex min-w-0 items-center gap-2">
2448
+ <AppIcon
2449
+ size={14}
2450
+ className="text-muted-foreground"
2451
+ />
2452
+ <span className="truncate text-sm font-medium">
2453
+ {app.label}
2454
+ </span>
2455
+ {app.recommended ? (
2456
+ <Pill className="h-5 border-border bg-muted px-1.5 text-[11px]">
2457
+ {t("integrations.suggested")}
2458
+ </Pill>
2459
+ ) : null}
2460
+ </span>
2461
+ <Checkbox
2462
+ id={`setup-app-${app.id}`}
2463
+ checked={selected}
2464
+ onCheckedChange={(checked) =>
2465
+ onChange({
2466
+ ...form,
2467
+ selectedApps: checked
2468
+ ? Array.from(
2469
+ new Set([
2470
+ ...selectedApps,
2471
+ app.id,
2472
+ ]),
2473
+ )
2474
+ : selectedApps.filter(
2475
+ (appId) => appId !== app.id,
2476
+ ),
2477
+ })
2478
+ }
2479
+ aria-label={t(
2480
+ "integrations.appGrantAria",
2481
+ {
2482
+ action: selected
2483
+ ? t("integrations.remove")
2484
+ : t("integrations.grant"),
2485
+ app: app.label,
2486
+ },
2487
+ )}
2488
+ />
2489
+ </Label>
2490
+ );
2491
+ })}
2492
+ </div>
2493
+ </div>
2494
+ ) : null}
2495
+ </div>
2496
+ </details>
1894
2497
  ) : null}
1895
2498
  </div>
1896
- ) : null}
1897
- </div>
1898
- ) : null}
1899
- </>
1900
- ) : null}
2499
+ <UserAccessControl
2500
+ allUsers={form.userGrantMode === "all-users"}
2501
+ selectedUsers={selectedUsers}
2502
+ selectedUserGroups={selectedUserGroups}
2503
+ groups={groups}
2504
+ canManageGroups={canManageGroups}
2505
+ onManageGroups={onManageGroups}
2506
+ onEditGroup={onEditGroup}
2507
+ onChange={({
2508
+ allUsers,
2509
+ selectedUsers: nextUsers,
2510
+ selectedUserGroups: nextGroups,
2511
+ }) =>
2512
+ onChange({
2513
+ ...form,
2514
+ userGrantMode: allUsers
2515
+ ? "all-users"
2516
+ : "selected-users",
2517
+ selectedUsers: nextUsers,
2518
+ selectedUserGroups: nextGroups,
2519
+ })
2520
+ }
2521
+ />
2522
+ </div>
2523
+ ) : null}
2524
+ </>
2525
+ ) : null}
2526
+ </div>
1901
2527
  </div>
1902
2528
 
1903
- <DialogFooter className="border-t p-4">
1904
- <Button
1905
- type="button"
1906
- variant="outline"
1907
- onClick={onClose}
1908
- disabled={saving}
1909
- >
1910
- {t("integrations.cancel")}
1911
- </Button>
1912
- {step > 0 ? (
2529
+ <DialogFooter className="shrink-0 border-t px-6 py-4 sm:px-10">
2530
+ <div className="mx-auto flex w-full max-w-2xl items-center justify-between gap-2">
1913
2531
  <Button
1914
2532
  type="button"
1915
- variant="outline"
1916
- onClick={() => onStepChange(step - 1)}
2533
+ variant="ghost"
2534
+ onClick={onClose}
1917
2535
  disabled={saving}
1918
2536
  >
1919
- <IconArrowLeft size={14} className="rtl:-scale-x-100" />
1920
- {t("integrations.back")}
1921
- </Button>
1922
- ) : null}
1923
- {step < 2 ? (
1924
- <Button
1925
- type="button"
1926
- onClick={() => onStepChange(step + 1)}
1927
- disabled={!canAdvance || loading || saving}
1928
- >
1929
- {t("integrations.next")}
1930
- <IconArrowRight size={14} className="rtl:-scale-x-100" />
1931
- </Button>
1932
- ) : (
1933
- <Button
1934
- type="button"
1935
- onClick={onSubmit}
1936
- disabled={!canAdvance || loading || saving}
1937
- >
1938
- {saving ? <IconRefresh size={14} className="animate-spin" /> : null}
1939
- {state?.mode === "repair"
1940
- ? t("integrations.applyRepair")
1941
- : t("integrations.createConnection")}
2537
+ {t("integrations.cancel")}
1942
2538
  </Button>
1943
- )}
2539
+ <div className="flex items-center gap-2">
2540
+ {step > 0 ? (
2541
+ <Button
2542
+ type="button"
2543
+ variant="outline"
2544
+ onClick={() => onStepChange(step - 1)}
2545
+ disabled={saving}
2546
+ >
2547
+ <IconArrowLeft size={14} className="rtl:-scale-x-100" />
2548
+ {t("integrations.back")}
2549
+ </Button>
2550
+ ) : null}
2551
+ {!isAccessStep ? (
2552
+ <Button
2553
+ type="button"
2554
+ onClick={() => onStepChange(step + 1)}
2555
+ disabled={!canAdvance || loading || saving}
2556
+ >
2557
+ {t("integrations.next")}
2558
+ <IconArrowRight size={14} className="rtl:-scale-x-100" />
2559
+ </Button>
2560
+ ) : (
2561
+ <Button
2562
+ type="button"
2563
+ onClick={onSubmit}
2564
+ disabled={!canAdvance || loading || saving}
2565
+ >
2566
+ {saving ? (
2567
+ <IconRefresh size={14} className="animate-spin" />
2568
+ ) : null}
2569
+ {state?.mode === "repair"
2570
+ ? t("integrations.applyRepair")
2571
+ : t("integrations.createConnection")}
2572
+ </Button>
2573
+ )}
2574
+ </div>
2575
+ </div>
1944
2576
  </DialogFooter>
1945
2577
  </Modal>
1946
2578
  );
1947
2579
  }
1948
2580
 
1949
- function GrantPreview({
1950
- providerLabel,
1951
- grantMode,
1952
- selectedAppIds,
1953
- grantApps,
1954
- }: {
1955
- providerLabel: string;
1956
- grantMode: SetupWizardFormState["grantMode"];
1957
- selectedAppIds: string[];
1958
- grantApps: GrantApp[];
1959
- }) {
1960
- const t = useT();
1961
- const selectedLabel = summarizeAppList(selectedAppIds, grantApps, t);
1962
- return (
1963
- <div className="rounded-md border bg-muted/30 px-3 py-2 text-sm">
1964
- <div className="flex items-center gap-2 font-medium text-foreground">
1965
- <IconShieldCheck size={14} className="text-muted-foreground" />
1966
- {t("integrations.grantPreview")}
1967
- </div>
1968
- <p className="mt-1 text-xs leading-5 text-muted-foreground">
1969
- {grantMode === "all-apps"
1970
- ? t("integrations.grantPreviewAllApps", {
1971
- provider: providerLabel,
1972
- })
1973
- : selectedAppIds.length > 0
1974
- ? t("integrations.grantPreviewSelected", {
1975
- apps: selectedLabel,
1976
- provider: providerLabel,
1977
- })
1978
- : t("integrations.grantPreviewEmpty", {
1979
- provider: providerLabel,
1980
- })}
1981
- </p>
1982
- </div>
1983
- );
1984
- }
1985
-
1986
2581
  function DeleteConfirm({
1987
2582
  connection,
1988
2583
  deleting,
@@ -2057,7 +2652,7 @@ function ConnectionDeleteImpact({
2057
2652
  const t = useT();
2058
2653
  if (loading) {
2059
2654
  return (
2060
- <div className="rounded-md border bg-muted/30 px-3 py-2 text-sm text-muted-foreground">
2655
+ <div className="rounded-md bg-muted/30 px-3 py-2 text-sm text-muted-foreground">
2061
2656
  {t("integrations.checkingAffectedApps")}
2062
2657
  </div>
2063
2658
  );
@@ -2073,7 +2668,7 @@ function ConnectionDeleteImpact({
2073
2668
  );
2074
2669
 
2075
2670
  return (
2076
- <div className="rounded-md border bg-muted/30 px-3 py-2 text-sm">
2671
+ <div className="rounded-md bg-muted/30 px-3 py-2 text-sm">
2077
2672
  <div className="flex flex-wrap items-center gap-2">
2078
2673
  <Pill className="border-border bg-background text-muted-foreground">
2079
2674
  {impact.impactSummary.hasAllAppsAccess
@@ -2105,14 +2700,28 @@ function ConnectionDeleteImpact({
2105
2700
  export default function WorkspaceIntegrationsRoute() {
2106
2701
  const t = useT();
2107
2702
  const queryClient = useQueryClient();
2703
+ const { canManageOrg } = useOrgRole();
2704
+ const { data: dispatchAppRole } = useAppRoles("dispatch");
2705
+ const isDispatchAppAdmin = dispatchAppRole?.myRole === "admin";
2706
+ const canManageConnections = canManageOrg || isDispatchAppAdmin;
2108
2707
  const [form, setForm] = useState<ConnectionFormState | null>(null);
2109
2708
  const [setupWizard, setSetupWizard] = useState<SetupWizardState | null>(null);
2110
2709
  const [setupStep, setSetupStep] = useState(0);
2111
2710
  const [setupForm, setSetupForm] = useState<SetupWizardFormState | null>(null);
2112
2711
  const [setupFormKey, setSetupFormKey] = useState("");
2712
+ const [providerQuery, setProviderQuery] = useState("");
2713
+ const [providerChoice, setProviderChoice] = useState<ProviderChoice | null>(
2714
+ null,
2715
+ );
2716
+ const [personalIntegrationId, setPersonalIntegrationId] = useState<
2717
+ string | null
2718
+ >(null);
2113
2719
  const [deleteTarget, setDeleteTarget] = useState<WorkspaceConnection | null>(
2114
2720
  null,
2115
2721
  );
2722
+ const [groupEditor, setGroupEditor] = useState<{
2723
+ group: WorkspaceUserGroup | null;
2724
+ } | null>(null);
2116
2725
 
2117
2726
  const connectionsQuery = useActionQuery(
2118
2727
  "list-workspace-connections",
@@ -2122,6 +2731,11 @@ export default function WorkspaceIntegrationsRoute() {
2122
2731
  includeAgentCards: false,
2123
2732
  audience: "all",
2124
2733
  });
2734
+ const groupsQuery = useActionQuery(
2735
+ "list-workspace-user-groups",
2736
+ {},
2737
+ { enabled: canManageOrg },
2738
+ );
2125
2739
  const setupPlanQuery = useActionQuery<WorkspaceConnectionSetupPlan>(
2126
2740
  "plan-workspace-connection-setup",
2127
2741
  {
@@ -2136,6 +2750,7 @@ export default function WorkspaceIntegrationsRoute() {
2136
2750
  const providers = data.providers;
2137
2751
  const connections = data.connections;
2138
2752
  const apps = (appsQuery.data ?? []) as WorkspaceAppSummary[];
2753
+ const groups = (groupsQuery.data ?? []) as WorkspaceUserGroup[];
2139
2754
  const providersById = useMemo(
2140
2755
  () => new Map(providers.map((provider) => [provider.id, provider])),
2141
2756
  [providers],
@@ -2163,11 +2778,30 @@ export default function WorkspaceIntegrationsRoute() {
2163
2778
  }
2164
2779
  return map;
2165
2780
  }, [connections]);
2781
+ const personalIntegrations = useMemo(() => {
2782
+ const map = new Map<string, DefaultMcpIntegration>();
2783
+ for (const integration of getDefaultMcpIntegrations()) {
2784
+ map.set(integration.provider, integration);
2785
+ map.set(integration.id, integration);
2786
+ }
2787
+ return map;
2788
+ }, []);
2789
+ const filteredProviders = useMemo(() => {
2790
+ const query = providerQuery.trim().toLowerCase();
2791
+ if (!query) return providers;
2792
+ return providers.filter((provider) =>
2793
+ `${provider.label} ${provider.description} ${provider.capabilities.join(" ")}`
2794
+ .toLowerCase()
2795
+ .includes(query),
2796
+ );
2797
+ }, [providerQuery, providers]);
2166
2798
 
2167
2799
  const upsertConnection = useActionMutation("upsert-workspace-connection");
2168
2800
  const applySetup = useActionMutation("apply-workspace-connection-setup");
2169
2801
  const setGrant = useActionMutation("set-workspace-connection-grant");
2170
2802
  const deleteConnection = useActionMutation("delete-workspace-connection");
2803
+ const upsertGroup = useActionMutation("upsert-workspace-user-group");
2804
+ const createPersonalMcpServer = useCreateMcpServer();
2171
2805
 
2172
2806
  const currentSetupKey = setupWizard
2173
2807
  ? `${setupWizard.mode}:${setupWizard.connectionId ?? ""}:${
@@ -2178,18 +2812,45 @@ export default function WorkspaceIntegrationsRoute() {
2178
2812
  useEffect(() => {
2179
2813
  if (!setupWizard || !setupPlanQuery.data) return;
2180
2814
  if (setupFormKey === currentSetupKey) return;
2181
- setSetupForm(setupFormFromPlan(setupPlanQuery.data, setupWizard.mode));
2815
+ const nextForm = setupFormFromPlan(setupPlanQuery.data, setupWizard.mode);
2816
+ if (!canManageOrg) {
2817
+ nextForm.grantMode = "selected-apps";
2818
+ nextForm.selectedApps = ["dispatch"];
2819
+ }
2820
+ setSetupForm(nextForm);
2182
2821
  setSetupFormKey(currentSetupKey);
2183
- }, [currentSetupKey, setupFormKey, setupPlanQuery.data, setupWizard]);
2822
+ }, [
2823
+ canManageOrg,
2824
+ currentSetupKey,
2825
+ setupFormKey,
2826
+ setupPlanQuery.data,
2827
+ setupWizard,
2828
+ ]);
2184
2829
 
2185
2830
  function openSetup(provider: WorkspaceConnectionProvider) {
2831
+ if (!canManageConnections) return;
2186
2832
  setSetupWizard({ mode: "setup", providerId: provider.id });
2187
2833
  setSetupStep(0);
2188
2834
  setSetupForm(null);
2189
2835
  setSetupFormKey("");
2190
2836
  }
2191
2837
 
2838
+ function openProviderConnection(provider: WorkspaceConnectionProvider) {
2839
+ const personalIntegration = personalIntegrations.get(provider.id);
2840
+ if (personalIntegration) {
2841
+ setProviderChoice({ provider, personalIntegration });
2842
+ return;
2843
+ }
2844
+ if (!canManageConnections) return;
2845
+ if (providerHasOAuth(provider)) {
2846
+ startWorkspaceProviderOAuth(provider);
2847
+ return;
2848
+ }
2849
+ openSetup(provider);
2850
+ }
2851
+
2192
2852
  function openRepair(connection: WorkspaceConnection) {
2853
+ if (!canManageConnection(connection)) return;
2193
2854
  setSetupWizard({
2194
2855
  mode: "repair",
2195
2856
  providerId: connection.provider,
@@ -2201,14 +2862,85 @@ export default function WorkspaceIntegrationsRoute() {
2201
2862
  }
2202
2863
 
2203
2864
  function openEdit(connection: WorkspaceConnection) {
2865
+ if (!canManageConnection(connection)) return;
2204
2866
  setForm(
2205
2867
  formFromConnection(connection, providersById.get(connection.provider)),
2206
2868
  );
2207
2869
  }
2208
2870
 
2871
+ function openGroupEditor(group: WorkspaceUserGroup | null = null) {
2872
+ if (!canManageOrg) return;
2873
+ setGroupEditor({ group });
2874
+ }
2875
+
2876
+ function canManageConnection(connection: WorkspaceConnection): boolean {
2877
+ return (
2878
+ canManageOrg ||
2879
+ (isDispatchAppAdmin &&
2880
+ connection.allowedApps.length === 1 &&
2881
+ connection.allowedApps[0]?.toLowerCase() === "dispatch")
2882
+ );
2883
+ }
2884
+
2885
+ function canManageConnectionGrant(
2886
+ connection: WorkspaceConnection,
2887
+ appId: string,
2888
+ ): boolean {
2889
+ return (
2890
+ canManageOrg ||
2891
+ (isDispatchAppAdmin &&
2892
+ appId.toLowerCase() === "dispatch" &&
2893
+ connection.allowedApps.length > 1 &&
2894
+ connection.allowedApps.some(
2895
+ (allowedApp) => allowedApp.toLowerCase() === "dispatch",
2896
+ ))
2897
+ );
2898
+ }
2899
+
2900
+ async function handleSaveGroup(name: string, memberEmails: string[]) {
2901
+ if (!canManageOrg) return;
2902
+ try {
2903
+ await upsertGroup.mutateAsync({
2904
+ id: groupEditor?.group?.id,
2905
+ name,
2906
+ memberEmails,
2907
+ });
2908
+ toast.success(
2909
+ t(
2910
+ groupEditor?.group
2911
+ ? "integrations.groupUpdated"
2912
+ : "integrations.groupCreated",
2913
+ ),
2914
+ );
2915
+ setGroupEditor(null);
2916
+ queryClient.invalidateQueries({ queryKey: GROUP_QUERY_KEY });
2917
+ } catch (error) {
2918
+ toast.error(
2919
+ error instanceof Error
2920
+ ? error.message
2921
+ : t("integrations.groupSaveError"),
2922
+ );
2923
+ }
2924
+ }
2925
+
2209
2926
  async function handleSubmit(event: FormEvent<HTMLFormElement>) {
2210
2927
  event.preventDefault();
2211
2928
  if (!form) return;
2929
+ if (form.id) {
2930
+ const existing = connections.find(
2931
+ (connection) => connection.id === form.id,
2932
+ );
2933
+ if (existing && !canManageConnection(existing)) return;
2934
+ } else if (!canManageConnections) {
2935
+ return;
2936
+ }
2937
+ if (
2938
+ !canManageOrg &&
2939
+ (form.allApps ||
2940
+ form.selectedApps.some((appId) => appId.toLowerCase() !== "dispatch"))
2941
+ ) {
2942
+ return;
2943
+ }
2212
2944
  try {
2213
2945
  const provider = providersById.get(form.provider);
2214
2946
  const credentialRefs = normalizeCredentialRefs(
@@ -2225,6 +2957,8 @@ export default function WorkspaceIntegrationsRoute() {
2225
2957
  scopes: normalizeList(form.scopes),
2226
2958
  credentialRefs,
2227
2959
  allowedApps: form.allApps ? [] : form.selectedApps,
2960
+ allowedUsers: form.allUsers ? [] : form.selectedUsers,
2961
+ allowedUserGroups: form.allUsers ? [] : form.selectedUserGroups,
2228
2962
  });
2229
2963
  toast.success(
2230
2964
  form.id
@@ -2241,6 +2975,16 @@ export default function WorkspaceIntegrationsRoute() {
2241
2975
 
2242
2976
  async function applySetupWizard() {
2243
2977
  if (!setupForm) return;
2978
+ if (!canManageConnections) return;
2979
+ if (
2980
+ !canManageOrg &&
2981
+ (setupForm.grantMode === "all-apps" ||
2982
+ setupForm.selectedApps.some(
2983
+ (appId) => appId.toLowerCase() !== "dispatch",
2984
+ ))
2985
+ ) {
2986
+ return;
2987
+ }
2244
2988
  try {
2245
2989
  await applySetup.mutateAsync({
2246
2990
  connectionId: setupForm.connectionId,
@@ -2256,6 +3000,9 @@ export default function WorkspaceIntegrationsRoute() {
2256
3000
  ),
2257
3001
  grantMode: setupForm.grantMode,
2258
3002
  selectedApps: setupForm.selectedApps,
3003
+ userGrantMode: setupForm.userGrantMode,
3004
+ selectedUsers: setupForm.selectedUsers,
3005
+ selectedUserGroups: setupForm.selectedUserGroups,
2259
3006
  });
2260
3007
  toast.success(
2261
3008
  setupForm.connectionId
@@ -2280,6 +3027,7 @@ export default function WorkspaceIntegrationsRoute() {
2280
3027
  appId: string,
2281
3028
  granted: boolean,
2282
3029
  ) {
3030
+ if (!canManageConnectionGrant(connection, appId)) return;
2283
3031
  const previous =
2284
3032
  queryClient.getQueryData<WorkspaceConnectionsResponse>(
2285
3033
  CONNECTION_QUERY_KEY,
@@ -2358,6 +3106,7 @@ export default function WorkspaceIntegrationsRoute() {
2358
3106
 
2359
3107
  async function confirmDelete() {
2360
3108
  if (!deleteTarget) return;
3109
+ if (!canManageConnection(deleteTarget)) return;
2361
3110
  const previous =
2362
3111
  queryClient.getQueryData<WorkspaceConnectionsResponse>(
2363
3112
  CONNECTION_QUERY_KEY,
@@ -2431,133 +3180,193 @@ export default function WorkspaceIntegrationsRoute() {
2431
3180
  ) : null}
2432
3181
  {!connectionsQuery.isError && !appsQuery.isError ? (
2433
3182
  <>
2434
- <section
2435
- data-usage-tracked={usageTracked ? "true" : undefined}
2436
- className={cx(
2437
- "dispatch-integrations-summary-grid grid gap-3",
2438
- usageTracked && "dispatch-integrations-summary-grid-tracked",
2439
- )}
2440
- >
2441
- <SummaryCard
2442
- icon={IconCheck}
2443
- label={t("integrations.readyProviders")}
2444
- value={`${data.counts.readyProviders ?? 0}/${providers.length}`}
2445
- detail={t("integrations.readyProvidersDetail")}
2446
- />
2447
- <SummaryCard
2448
- icon={IconPlugConnected}
2449
- label={t("integrations.connections")}
2450
- value={String(connections.length)}
2451
- detail={t("integrations.connectedCount", {
2452
- count: connectedCount,
2453
- })}
2454
- />
2455
- <SummaryCard
2456
- icon={IconShieldCheck}
2457
- label={t("integrations.appGrants")}
2458
- value={String(data.grants.length)}
2459
- detail={t("integrations.appGrantsDetail", {
2460
- allAppCount: data.counts.allAppConnections ?? 0,
2461
- selectedCount: data.counts.selectedAppConnections ?? 0,
2462
- })}
2463
- />
2464
- <SummaryCard
2465
- icon={IconKey}
2466
- label={t("integrations.keyHealth")}
2467
- value={
2468
- connectionsMissingKeys === 0
2469
- ? t("integrations.healthy")
2470
- : t("integrations.missingCount", {
2471
- count: connectionsMissingKeys,
2472
- })
2473
- }
2474
- detail={
2475
- attentionCount
2476
- ? t("integrations.connectionNeedsAttention", {
2477
- count: attentionCount,
2478
- })
2479
- : t("integrations.requiredRefsPresent")
2480
- }
2481
- />
2482
- {usageTracked ? (
2483
- <SummaryCard
2484
- icon={IconClock}
2485
- label={t("integrations.usage")}
2486
- value={
2487
- Number.isFinite(mostRecentUsage)
2488
- ? formatTimestamp(
2489
- t,
2490
- new Date(mostRecentUsage).toISOString(),
2491
- )
2492
- : t("integrations.time.neverUsed")
2493
- }
2494
- detail={
2495
- neverUsedCount
2496
- ? t("integrations.neverUsedCount", {
2497
- count: neverUsedCount,
2498
- })
2499
- : t("integrations.allTrackedAccountsHaveUsage")
2500
- }
2501
- />
2502
- ) : null}
2503
- </section>
2504
-
2505
- <IntegrationOnboarding />
2506
-
2507
3183
  {connectionsQuery.isLoading ? (
2508
- <div className="rounded-lg border border-dashed px-6 py-12 text-center text-sm text-muted-foreground">
3184
+ <div className="rounded-lg bg-muted/30 px-6 py-12 text-center text-sm text-muted-foreground">
2509
3185
  {t("integrations.loadingWorkspaceIntegrations")}
2510
3186
  </div>
2511
3187
  ) : null}
2512
3188
 
2513
3189
  <section>
2514
- <div className="mb-3 flex items-center justify-between gap-3">
3190
+ <div className="mb-3 flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
2515
3191
  <div>
2516
3192
  <h2 className="text-sm font-semibold text-foreground">
2517
- {t("integrations.providerCatalog")}
3193
+ {t(
3194
+ /* i18n-key-ignore */
3195
+ "integrations.workspaceCatalogTitle",
3196
+ { defaultValue: "Workspace integrations" },
3197
+ )}
2518
3198
  </h2>
2519
3199
  <p className="text-xs text-muted-foreground">
2520
- {t("integrations.providersAvailable", {
2521
- count: providers.length,
2522
- })}
3200
+ {t(
3201
+ /* i18n-key-ignore */
3202
+ "integrations.workspaceCatalogDescription",
3203
+ {
3204
+ defaultValue:
3205
+ "Connect once, then choose which apps can use it.",
3206
+ },
3207
+ )}
2523
3208
  </p>
2524
3209
  </div>
2525
- </div>
2526
- <div className="dispatch-provider-grid grid gap-3">
2527
- {providers.map((provider) => (
2528
- <ProviderCard
2529
- key={provider.id}
2530
- provider={provider}
2531
- connections={providerConnections.get(provider.id) ?? []}
2532
- onCreate={() => openSetup(provider)}
3210
+ <div className="relative w-full sm:max-w-xs">
3211
+ <IconSearch className="pointer-events-none absolute start-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
3212
+ <Input
3213
+ value={providerQuery}
3214
+ onChange={(event) => setProviderQuery(event.target.value)}
3215
+ placeholder={t(
3216
+ /* i18n-key-ignore */
3217
+ "integrations.searchWorkspacePlaceholder",
3218
+ { defaultValue: "Search integrations" },
3219
+ )}
3220
+ aria-label={t(
3221
+ /* i18n-key-ignore */
3222
+ "integrations.searchWorkspaceLabel",
3223
+ { defaultValue: "Search workspace integrations" },
3224
+ )}
3225
+ className="h-9 ps-9"
2533
3226
  />
2534
- ))}
3227
+ </div>
2535
3228
  </div>
3229
+ <IntegrationGrid
3230
+ items={filteredProviders.map((provider) => {
3231
+ const active = (
3232
+ providerConnections.get(provider.id) ?? []
3233
+ ).filter((connection) => connection.status !== "disabled");
3234
+ const connected = active.length > 0;
3235
+ return {
3236
+ id: provider.id,
3237
+ name: provider.label,
3238
+ description: provider.description,
3239
+ logo: logoForProvider(provider.id, provider.label),
3240
+ status: connected ? "Connected" : undefined,
3241
+ statusClassName: connected
3242
+ ? "text-emerald-600 dark:text-emerald-400"
3243
+ : "text-muted-foreground",
3244
+ actionLabel: connected
3245
+ ? canManageConnection(active[0])
3246
+ ? "Manage"
3247
+ : personalIntegrations.has(provider.id)
3248
+ ? "Connect for me"
3249
+ : "Admin only"
3250
+ : "Connect",
3251
+ disabled:
3252
+ !connected &&
3253
+ !canManageConnections &&
3254
+ !personalIntegrations.has(provider.id),
3255
+ onAction: () =>
3256
+ connected
3257
+ ? canManageConnection(active[0])
3258
+ ? openEdit(active[0])
3259
+ : openProviderConnection(provider)
3260
+ : openProviderConnection(provider),
3261
+ };
3262
+ })}
3263
+ emptyLabel="No integrations match."
3264
+ />
2536
3265
  </section>
2537
3266
 
3267
+ <details className="rounded-xl bg-muted/30">
3268
+ <summary className="cursor-pointer list-none px-4 py-3 text-sm font-medium text-foreground marker:hidden">
3269
+ {t(/* i18n-key-ignore */ "integrations.workspaceOverview", {
3270
+ defaultValue: "Workspace overview",
3271
+ })}
3272
+ </summary>
3273
+ <div className="p-4">
3274
+ <section
3275
+ data-usage-tracked={usageTracked ? "true" : undefined}
3276
+ className={cx(
3277
+ "dispatch-integrations-summary-grid grid gap-3",
3278
+ usageTracked &&
3279
+ "dispatch-integrations-summary-grid-tracked",
3280
+ )}
3281
+ >
3282
+ <SummaryCard
3283
+ icon={IconCheck}
3284
+ label={t("integrations.readyProviders")}
3285
+ value={`${data.counts.readyProviders ?? 0}/${providers.length}`}
3286
+ detail={t("integrations.readyProvidersDetail")}
3287
+ />
3288
+ <SummaryCard
3289
+ icon={IconPlugConnected}
3290
+ label={t("integrations.connections")}
3291
+ value={String(connections.length)}
3292
+ detail={t("integrations.connectedCount", {
3293
+ count: connectedCount,
3294
+ })}
3295
+ />
3296
+ <SummaryCard
3297
+ icon={IconShieldCheck}
3298
+ label={t("integrations.appGrants")}
3299
+ value={String(data.grants.length)}
3300
+ detail={t("integrations.appGrantsDetail", {
3301
+ allAppCount: data.counts.allAppConnections ?? 0,
3302
+ selectedCount: data.counts.selectedAppConnections ?? 0,
3303
+ })}
3304
+ />
3305
+ <SummaryCard
3306
+ icon={IconKey}
3307
+ label={t("integrations.keyHealth")}
3308
+ value={
3309
+ connectionsMissingKeys === 0
3310
+ ? t("integrations.healthy")
3311
+ : t("integrations.missingCount", {
3312
+ count: connectionsMissingKeys,
3313
+ })
3314
+ }
3315
+ detail={
3316
+ attentionCount
3317
+ ? t("integrations.connectionNeedsAttention", {
3318
+ count: attentionCount,
3319
+ })
3320
+ : t("integrations.requiredRefsPresent")
3321
+ }
3322
+ />
3323
+ {usageTracked ? (
3324
+ <SummaryCard
3325
+ icon={IconClock}
3326
+ label={t("integrations.usage")}
3327
+ value={
3328
+ Number.isFinite(mostRecentUsage)
3329
+ ? formatTimestamp(
3330
+ t,
3331
+ new Date(mostRecentUsage).toISOString(),
3332
+ )
3333
+ : t("integrations.time.neverUsed")
3334
+ }
3335
+ detail={
3336
+ neverUsedCount
3337
+ ? t("integrations.neverUsedCount", {
3338
+ count: neverUsedCount,
3339
+ })
3340
+ : t("integrations.allTrackedAccountsHaveUsage")
3341
+ }
3342
+ />
3343
+ ) : null}
3344
+ </section>
3345
+ </div>
3346
+ </details>
3347
+
2538
3348
  <section>
2539
3349
  <div className="mb-3 flex items-center justify-between gap-3">
2540
3350
  <div>
2541
3351
  <h2 className="text-sm font-semibold text-foreground">
2542
3352
  {t("integrations.connectedAccounts")}
2543
3353
  </h2>
2544
- <p className="text-xs text-muted-foreground">
2545
- {connections.length === 0
2546
- ? t("integrations.noSharedConnectionsYet")
2547
- : t("integrations.savedConnectionCount", {
2548
- count: connections.length,
2549
- })}
3354
+ <p className="mt-0.5 text-xs text-muted-foreground">
3355
+ {t("integrations.sharedConnectionsDescription", {
3356
+ defaultValue:
3357
+ "Saved provider connections that your apps can use.",
3358
+ })}
2550
3359
  </p>
2551
3360
  </div>
2552
3361
  </div>
2553
3362
  {connections.length === 0 && !connectionsQuery.isLoading ? (
2554
- <div className="rounded-lg border border-dashed px-6 py-12 text-center">
3363
+ <div className="rounded-lg bg-muted/30 px-6 py-12 text-center">
2555
3364
  <IconPlugConnected
2556
3365
  size={24}
2557
3366
  className="mx-auto text-muted-foreground"
2558
3367
  />
2559
3368
  <p className="mt-3 text-sm font-medium text-foreground">
2560
- {t("integrations.noSharedAccountsYet")}
3369
+ {t("integrations.noSharedConnectionsYet")}
2561
3370
  </p>
2562
3371
  <p className="mx-auto mt-1 max-w-md text-sm leading-5 text-muted-foreground">
2563
3372
  {t("integrations.emptyConnectedAccountsDescription")}
@@ -2572,7 +3381,12 @@ export default function WorkspaceIntegrationsRoute() {
2572
3381
  provider={providersById.get(connection.provider)}
2573
3382
  grantApps={grantApps}
2574
3383
  grants={data.grants}
3384
+ groups={groups}
2575
3385
  grantPending={setGrant.isPending}
3386
+ canManage={canManageConnection(connection)}
3387
+ canManageGrant={(appId) =>
3388
+ canManageConnectionGrant(connection, appId)
3389
+ }
2576
3390
  onEdit={() => openEdit(connection)}
2577
3391
  onRepair={() => openRepair(connection)}
2578
3392
  onDelete={() => setDeleteTarget(connection)}
@@ -2596,15 +3410,12 @@ export default function WorkspaceIntegrationsRoute() {
2596
3410
  form={setupForm}
2597
3411
  providers={providers}
2598
3412
  grantApps={grantApps}
3413
+ groups={groups}
3414
+ canManageGroups={canManageOrg}
3415
+ canManageAllApps={canManageOrg}
2599
3416
  loading={setupPlanQuery.isLoading}
2600
3417
  saving={applySetup.isPending}
2601
3418
  onStepChange={setSetupStep}
2602
- onProviderChange={(providerId) => {
2603
- setSetupWizard({ mode: "setup", providerId });
2604
- setSetupStep(0);
2605
- setSetupForm(null);
2606
- setSetupFormKey("");
2607
- }}
2608
3419
  onChange={setSetupForm}
2609
3420
  onClose={() => {
2610
3421
  setSetupWizard(null);
@@ -2612,16 +3423,88 @@ export default function WorkspaceIntegrationsRoute() {
2612
3423
  setSetupFormKey("");
2613
3424
  }}
2614
3425
  onSubmit={applySetupWizard}
3426
+ onManageGroups={() => openGroupEditor()}
3427
+ onEditGroup={openGroupEditor}
2615
3428
  />
2616
3429
  <ConnectionForm
2617
3430
  open={!!form}
2618
3431
  form={form}
2619
3432
  providers={providers}
2620
3433
  grantApps={grantApps}
3434
+ groups={groups}
3435
+ canManageGroups={canManageOrg}
3436
+ canManageAllApps={canManageOrg}
2621
3437
  saving={upsertConnection.isPending}
2622
3438
  onChange={setForm}
2623
3439
  onClose={() => setForm(null)}
2624
3440
  onSubmit={handleSubmit}
3441
+ onManageGroups={() => openGroupEditor()}
3442
+ onEditGroup={openGroupEditor}
3443
+ />
3444
+ <Dialog
3445
+ open={!!providerChoice}
3446
+ onOpenChange={(open) => !open && setProviderChoice(null)}
3447
+ >
3448
+ <DialogContent
3449
+ aria-describedby={undefined}
3450
+ className="inset-0 flex h-[100dvh] max-h-none w-full max-w-none translate-x-0 translate-y-0 flex-col overflow-hidden rounded-none p-0"
3451
+ >
3452
+ <DialogHeader className="sr-only">
3453
+ <DialogTitle>
3454
+ {providerChoice
3455
+ ? `Connect ${providerChoice.provider.label}`
3456
+ : "Connect integration"}
3457
+ </DialogTitle>
3458
+ </DialogHeader>
3459
+ {providerChoice ? (
3460
+ <IntegrationConnectionChoice
3461
+ name={providerChoice.provider.label}
3462
+ logo={logoForProvider(
3463
+ providerChoice.provider.id,
3464
+ providerChoice.provider.label,
3465
+ )}
3466
+ showWorkspaceOption={canManageConnections}
3467
+ onPersonal={() => {
3468
+ setPersonalIntegrationId(providerChoice.personalIntegration.id);
3469
+ setProviderChoice(null);
3470
+ }}
3471
+ onWorkspace={() => {
3472
+ const provider = providerChoice.provider;
3473
+ setProviderChoice(null);
3474
+ if (providerHasOAuth(provider)) {
3475
+ startWorkspaceProviderOAuth(provider);
3476
+ } else {
3477
+ openSetup(provider);
3478
+ }
3479
+ }}
3480
+ />
3481
+ ) : null}
3482
+ </DialogContent>
3483
+ </Dialog>
3484
+ {personalIntegrationId ? (
3485
+ <McpIntegrationDialog
3486
+ open
3487
+ integrations={[
3488
+ personalIntegrations.get(personalIntegrationId),
3489
+ ].filter((integration): integration is DefaultMcpIntegration =>
3490
+ Boolean(integration),
3491
+ )}
3492
+ connectIntegrationId={personalIntegrationId}
3493
+ defaultScope="user"
3494
+ canCreateOrgMcp={false}
3495
+ hasOrg={false}
3496
+ onOpenChange={(open) => {
3497
+ if (!open) setPersonalIntegrationId(null);
3498
+ }}
3499
+ onCreateMcpServer={createPersonalMcpServer.mutateAsync}
3500
+ />
3501
+ ) : null}
3502
+ <GroupEditor
3503
+ open={groupEditor !== null}
3504
+ group={groupEditor?.group ?? null}
3505
+ saving={upsertGroup.isPending}
3506
+ onClose={() => setGroupEditor(null)}
3507
+ onSave={handleSaveGroup}
2625
3508
  />
2626
3509
  <DeleteConfirm
2627
3510
  connection={deleteTarget}
@@ -2658,7 +3541,7 @@ function IntegrationOnboarding() {
2658
3541
  ];
2659
3542
 
2660
3543
  return (
2661
- <section className="rounded-lg border bg-card px-4 py-3 shadow-sm">
3544
+ <section className="rounded-lg bg-muted/30 px-4 py-3">
2662
3545
  <div className="dispatch-onboarding-grid grid gap-3">
2663
3546
  <div>
2664
3547
  <h2 className="text-sm font-semibold text-foreground">
@@ -2673,7 +3556,7 @@ function IntegrationOnboarding() {
2673
3556
  const Icon = step.icon;
2674
3557
  return (
2675
3558
  <div key={step.title} className="flex min-w-0 gap-2">
2676
- <div className="mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-md border bg-muted">
3559
+ <div className="mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-md bg-muted/50">
2677
3560
  <Icon size={14} className="text-muted-foreground" />
2678
3561
  </div>
2679
3562
  <div className="min-w-0">
@@ -2705,10 +3588,10 @@ function SummaryCard({
2705
3588
  detail: string;
2706
3589
  }) {
2707
3590
  return (
2708
- <div className="rounded-lg border bg-card p-4 shadow-sm">
3591
+ <div className="rounded-lg bg-muted/30 p-4">
2709
3592
  <div className="flex items-center justify-between gap-3">
2710
3593
  <span className="text-sm text-muted-foreground">{label}</span>
2711
- <div className="flex h-8 w-8 items-center justify-center rounded-md border bg-muted">
3594
+ <div className="flex h-8 w-8 items-center justify-center rounded-md bg-muted/50">
2712
3595
  <Icon size={16} className="text-muted-foreground" />
2713
3596
  </div>
2714
3597
  </div>