@agent-native/core 0.141.7 → 0.144.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 (328) hide show
  1. package/agent-native.eject.json +9 -0
  2. package/corpus/README.md +1 -1
  3. package/corpus/templates/analytics/.agents/skills/provider-api/SKILL.md +19 -0
  4. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +296 -0
  5. package/corpus/templates/analytics/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  6. package/corpus/templates/analytics/.agents/skills/turn-into-skill/SKILL.md +112 -0
  7. package/corpus/templates/analytics/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  8. package/corpus/templates/analytics/AGENTS.md +35 -38
  9. package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +1 -1
  10. package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +17 -22
  11. package/corpus/templates/analytics/actions/navigate.ts +4 -5
  12. package/corpus/templates/analytics/actions/provider-api-register.ts +50 -0
  13. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +44 -11
  14. package/corpus/templates/analytics/actions/test-custom-api-connection.ts +106 -0
  15. package/corpus/templates/analytics/app/components/CustomApiCard.tsx +430 -0
  16. package/corpus/templates/analytics/app/i18n-data.ts +680 -0
  17. package/corpus/templates/analytics/app/lib/native-mcp-exclusions.ts +6 -0
  18. package/corpus/templates/analytics/app/lib/sql-query.ts +33 -6
  19. package/corpus/templates/analytics/app/pages/DataSources.tsx +4 -0
  20. package/corpus/templates/analytics/changelog/2026-08-06-analytics-dashboards-load-reliably-during-background-data-sw.md +6 -0
  21. package/corpus/templates/analytics/changelog/2026-08-06-analytics-dashboards-remain-responsive-while-historical-data.md +6 -0
  22. package/corpus/templates/analytics/changelog/2026-08-06-analytics-query-results-now-render-as-a-table-with-csv-downl.md +6 -0
  23. package/corpus/templates/analytics/changelog/2026-08-06-connect-public-https-custom-apis-and-hand-tested-endpoints-t.md +6 -0
  24. package/corpus/templates/analytics/changelog/2026-08-06-durable-agent-worker-memory.md +6 -0
  25. package/corpus/templates/analytics/changelog/2026-08-06-live-dashboard-panels-use-the-shared-query-timeout.md +6 -0
  26. package/corpus/templates/analytics/changelog/2026-08-06-on-demand-billing-is-available-as-a-native-dashboard-with-ta.md +6 -0
  27. package/corpus/templates/analytics/changelog/2026-08-06-visible-dashboard-names-are-unique.md +6 -0
  28. package/corpus/templates/analytics/netlify.toml +7 -0
  29. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +4 -0
  30. package/corpus/templates/analytics/server/db/index.ts +27 -0
  31. package/corpus/templates/analytics/server/db/schema.ts +10 -0
  32. package/corpus/templates/analytics/server/lib/dashboards-store.ts +227 -60
  33. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +133 -44
  34. package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +227 -1
  35. package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
  36. package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +1 -0
  37. package/corpus/templates/analytics/server/plugins/db.ts +52 -18
  38. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +28 -2
  39. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +16 -1
  40. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +296 -0
  41. package/corpus/templates/assets/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  42. package/corpus/templates/assets/.agents/skills/turn-into-skill/SKILL.md +112 -0
  43. package/corpus/templates/assets/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  44. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +296 -0
  45. package/corpus/templates/brain/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  46. package/corpus/templates/brain/.agents/skills/turn-into-skill/SKILL.md +112 -0
  47. package/corpus/templates/brain/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  48. package/corpus/templates/brain/actions/list-connection-providers.ts +24 -3
  49. package/corpus/templates/brain/app/lib/dispatch-links.ts +44 -0
  50. package/corpus/templates/brain/app/routes/sources.tsx +15 -7
  51. package/corpus/templates/brain/changelog/2026-08-06-brain-provider-setup-links-open-dispatch-correctly-in-standa.md +6 -0
  52. package/corpus/templates/brain/server/plugins/agent-chat.ts +1 -0
  53. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +26 -0
  54. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +296 -0
  55. package/corpus/templates/calendar/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  56. package/corpus/templates/calendar/.agents/skills/turn-into-skill/SKILL.md +112 -0
  57. package/corpus/templates/calendar/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  58. package/corpus/templates/calendar/AGENTS.md +11 -32
  59. package/corpus/templates/calendar/actions/update-event.ts +107 -1
  60. package/corpus/templates/calendar/app/components/calendar/EventCalendarSelect.tsx +83 -0
  61. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +57 -0
  62. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +59 -73
  63. package/corpus/templates/calendar/app/hooks/use-events.ts +4 -1
  64. package/corpus/templates/calendar/app/i18n-data.ts +20 -0
  65. package/corpus/templates/calendar/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
  66. package/corpus/templates/calendar/changelog/2026-08-06-existing-events-can-move-between-connected-google-account-ca.md +6 -0
  67. package/corpus/templates/calendar/server/lib/google-calendar.ts +101 -0
  68. package/corpus/templates/calendar/server/plugins/agent-chat.ts +7 -1
  69. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
  70. package/corpus/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  71. package/corpus/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
  72. package/corpus/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  73. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +296 -0
  74. package/corpus/templates/clips/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  75. package/corpus/templates/clips/.agents/skills/turn-into-skill/SKILL.md +112 -0
  76. package/corpus/templates/clips/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  77. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +9 -24
  78. package/corpus/templates/clips/app/components/editor/timeline-geometry.ts +15 -0
  79. package/corpus/templates/clips/app/components/editor/waveform.tsx +4 -17
  80. package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +14 -3
  81. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +28 -38
  82. package/corpus/templates/clips/app/routes/share.$shareId.tsx +21 -27
  83. package/corpus/templates/clips/changelog/2026-08-06-clips-recovers-from-stalled-recording-setup-without-requirin.md +6 -0
  84. package/corpus/templates/clips/changelog/2026-08-06-clips-starts-faster-after-a-deploy-or-idle-period-startup-no.md +6 -0
  85. package/corpus/templates/clips/changelog/2026-08-06-deleting-a-recording-returns-cleanly-to-the-library-and-fit-.md +6 -0
  86. package/corpus/templates/clips/changelog/2026-08-06-hide-clip-insights-from-viewers-without-analytics-access.md +6 -0
  87. package/corpus/templates/clips/desktop/src/app.tsx +48 -20
  88. package/corpus/templates/clips/desktop/src/lib/recorder.ts +275 -74
  89. package/corpus/templates/clips/desktop/src/lib/recording-start-guard.ts +84 -0
  90. package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +14 -4
  91. package/corpus/templates/clips/server/plugins/db.ts +214 -26
  92. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +296 -0
  93. package/corpus/templates/content/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  94. package/corpus/templates/content/.agents/skills/turn-into-skill/SKILL.md +112 -0
  95. package/corpus/templates/content/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  96. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +296 -0
  97. package/corpus/templates/crm/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  98. package/corpus/templates/crm/.agents/skills/turn-into-skill/SKILL.md +112 -0
  99. package/corpus/templates/crm/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  100. package/corpus/templates/crm/changelog/2026-08-06-crm-starts-faster-after-a-deploy-or-idle-period-one-time-dat.md +6 -0
  101. package/corpus/templates/crm/server/plugins/db.ts +11 -8
  102. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +36 -1
  103. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +296 -0
  104. package/corpus/templates/design/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  105. package/corpus/templates/design/.agents/skills/turn-into-skill/SKILL.md +112 -0
  106. package/corpus/templates/design/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  107. package/corpus/templates/design/actions/import-from-url.ts +1 -1
  108. package/corpus/templates/design/app/components/design/LayersPanel.tsx +27 -18
  109. package/corpus/templates/design/app/pages/Index.tsx +6 -2
  110. package/corpus/templates/design/changelog/2026-08-05-deleting-a-design-from-the-home-page-no-longer-leaves-the-ap.md +6 -0
  111. package/corpus/templates/design/changelog/2026-08-05-renaming-a-layer-now-focuses-its-name-field-automatically.md +6 -0
  112. package/corpus/templates/design/server/plugins/agent-chat.ts +2 -0
  113. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +296 -0
  114. package/corpus/templates/dispatch/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  115. package/corpus/templates/dispatch/.agents/skills/turn-into-skill/SKILL.md +112 -0
  116. package/corpus/templates/dispatch/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  117. package/corpus/templates/dispatch/changelog/2026-08-06-ready-workspace-apps-stay-one-click-away-in-a-compact-dispat.md +6 -0
  118. package/corpus/templates/dispatch/changelog/2026-08-06-shared-vault-values-and-management-are-now-restricted-to-wor.md +6 -0
  119. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +296 -0
  120. package/corpus/templates/factory/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  121. package/corpus/templates/factory/.agents/skills/turn-into-skill/SKILL.md +112 -0
  122. package/corpus/templates/factory/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  123. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +296 -0
  124. package/corpus/templates/forms/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  125. package/corpus/templates/forms/.agents/skills/turn-into-skill/SKILL.md +112 -0
  126. package/corpus/templates/forms/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  127. package/corpus/templates/forms/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
  128. package/corpus/templates/forms/server/plugins/agent-chat.ts +4 -0
  129. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +296 -0
  130. package/corpus/templates/macros/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  131. package/corpus/templates/macros/.agents/skills/turn-into-skill/SKILL.md +112 -0
  132. package/corpus/templates/macros/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  133. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +296 -0
  134. package/corpus/templates/mail/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  135. package/corpus/templates/mail/.agents/skills/turn-into-skill/SKILL.md +112 -0
  136. package/corpus/templates/mail/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  137. package/corpus/templates/mail/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
  138. package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
  139. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +296 -0
  140. package/corpus/templates/plan/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  141. package/corpus/templates/plan/.agents/skills/turn-into-skill/SKILL.md +112 -0
  142. package/corpus/templates/plan/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  143. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +48 -5
  144. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +37 -1
  145. package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +6 -0
  146. package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +15 -2
  147. package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +29 -24
  148. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +296 -0
  149. package/corpus/templates/slides/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  150. package/corpus/templates/slides/.agents/skills/turn-into-skill/SKILL.md +112 -0
  151. package/corpus/templates/slides/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  152. package/corpus/templates/slides/AGENTS.md +7 -9
  153. package/corpus/templates/slides/_gitignore +1 -0
  154. package/corpus/templates/slides/actions/export-pptx.ts +402 -36
  155. package/corpus/templates/slides/actions/import-file.ts +46 -85
  156. package/corpus/templates/slides/actions/import-google-slides-reference.ts +271 -12
  157. package/corpus/templates/slides/actions/import-pptx.ts +85 -60
  158. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +10 -13
  159. package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +5 -1
  160. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +25 -12
  161. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +112 -57
  162. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +202 -0
  163. package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +26 -14
  164. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +173 -0
  165. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +182 -141
  166. package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +1 -0
  167. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +18 -0
  168. package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +85 -0
  169. package/corpus/templates/slides/app/components/layout/Layout.tsx +3 -0
  170. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +4 -0
  171. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +27 -1
  172. package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
  173. package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -1
  174. package/corpus/templates/slides/app/lib/deck-preview-frame.ts +15 -0
  175. package/corpus/templates/slides/app/lib/deck-share-links.ts +9 -0
  176. package/corpus/templates/slides/app/lib/export-pptx-client.ts +89 -0
  177. package/corpus/templates/slides/app/lib/slide-animation-elements.ts +3 -0
  178. package/corpus/templates/slides/app/pages/Index.tsx +91 -1
  179. package/corpus/templates/slides/changelog/2026-08-06-clicking-the-gray-canvas-around-a-slide-clears-its-selection.md +6 -0
  180. package/corpus/templates/slides/changelog/2026-08-06-deck-thumbnails-keep-a-consistent-listing-frame-across-aspec.md +6 -0
  181. package/corpus/templates/slides/changelog/2026-08-06-deep-linked-decks-open-directly-instead-of-being-interrupted.md +6 -0
  182. package/corpus/templates/slides/changelog/2026-08-06-google-slides-exports-now-show-clear-import-steps-when-direc.md +6 -0
  183. package/corpus/templates/slides/changelog/2026-08-06-import-pdf-google-slides-and-powerpoint-decks-directly-from-.md +6 -0
  184. package/corpus/templates/slides/changelog/2026-08-06-imported-powerpoint-exports-keep-text-and-images-editable-an.md +6 -0
  185. package/corpus/templates/slides/changelog/2026-08-06-presentation-prompts-continue-adding-rendered-slides-until-t.md +6 -0
  186. package/corpus/templates/slides/changelog/2026-08-06-public-decks-now-share-read-only-presentation-links-by-defau.md +6 -0
  187. package/corpus/templates/slides/changelog/2026-08-06-slides-offers-a-direct-connect-google-button-when-a-drive-co.md +6 -0
  188. package/corpus/templates/slides/changelog/2026-08-06-slides-preserve-uploaded-pdf-layouts-accept-larger-reference.md +6 -0
  189. package/corpus/templates/slides/changelog/2026-08-06-styled-words-stay-part-of-their-text-box-instead-of-becoming.md +6 -0
  190. package/corpus/templates/slides/server/handlers/google-docs-auth.ts +7 -20
  191. package/corpus/templates/slides/server/handlers/import/html-converter.ts +236 -13
  192. package/corpus/templates/slides/server/handlers/import/pptx-assets.ts +67 -0
  193. package/corpus/templates/slides/server/handlers/import/pptx-parser.ts +2 -0
  194. package/corpus/templates/slides/server/handlers/uploads.ts +7 -3
  195. package/corpus/templates/slides/server/lib/chat-attachments.ts +6 -3
  196. package/corpus/templates/slides/server/lib/emails.ts +7 -2
  197. package/corpus/templates/slides/server/lib/google-docs-error.ts +50 -0
  198. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +1 -0
  199. package/corpus/templates/slides/server/lib/import-asset-storage.ts +127 -0
  200. package/corpus/templates/slides/server/plugins/agent-chat.ts +4 -3
  201. package/corpus/templates/slides/server/routes/api/import-assets/[token].get.ts +70 -0
  202. package/corpus/templates/slides/shared/upload-types.ts +3 -0
  203. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +296 -0
  204. package/corpus/templates/tasks/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  205. package/corpus/templates/tasks/.agents/skills/turn-into-skill/SKILL.md +112 -0
  206. package/corpus/templates/tasks/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  207. package/dist/agent/production-agent.d.ts +17 -0
  208. package/dist/agent/production-agent.js +72 -1
  209. package/dist/cli/agent-plugin.d.ts +112 -0
  210. package/dist/cli/agent-plugin.js +1081 -0
  211. package/dist/cli/app-skill.js +39 -1
  212. package/dist/cli/code-agent-executor.d.ts +3 -0
  213. package/dist/cli/code-agent-executor.js +51 -13
  214. package/dist/cli/index.js +13 -0
  215. package/dist/cli/templates-meta.js +1 -1
  216. package/dist/client/AgentPanel.d.ts +3 -1
  217. package/dist/client/AgentPanel.js +9 -2
  218. package/dist/client/AssistantChat.d.ts +8 -0
  219. package/dist/client/AssistantChat.js +23 -8
  220. package/dist/client/NewWorkspaceAppFlow.js +1 -0
  221. package/dist/client/agent-chat-adapter.js +123 -4
  222. package/dist/client/assistant-ui-recovery.d.ts +2 -1
  223. package/dist/client/assistant-ui-recovery.js +7 -3
  224. package/dist/client/onboarding/FirstRunOnboarding.js +11 -4
  225. package/dist/client/org/TeamPage.js +1 -1
  226. package/dist/client/require-session.js +1 -1
  227. package/dist/client/resources/McpConnectionSuggestion.d.ts +1 -0
  228. package/dist/client/resources/McpConnectionSuggestion.js +27 -10
  229. package/dist/client/resources/McpIntegrationDialog.js +77 -45
  230. package/dist/client/resources/mcp-integration-catalog.d.ts +13 -1
  231. package/dist/client/resources/mcp-integration-catalog.js +255 -2
  232. package/dist/client/resources/mcp-integration-logos.js +18 -2
  233. package/dist/client/settings/AccountSettingsCard.js +2 -1
  234. package/dist/collab/routes.d.ts +1 -1
  235. package/dist/collab/struct-routes.d.ts +1 -1
  236. package/dist/db/client.d.ts +9 -0
  237. package/dist/db/client.js +51 -16
  238. package/dist/db/create-get-db.js +2 -2
  239. package/dist/db/ddl-guard.d.ts +11 -10
  240. package/dist/db/ddl-guard.js +40 -12
  241. package/dist/deploy/build.d.ts +2 -0
  242. package/dist/deploy/build.js +97 -4
  243. package/dist/deploy/workspace-deploy.js +0 -2
  244. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  245. package/dist/guards/no-env-credentials.js +9 -0
  246. package/dist/ingestion/index.d.ts +1 -1
  247. package/dist/ingestion/pptx.d.ts +68 -0
  248. package/dist/ingestion/pptx.js +660 -213
  249. package/dist/integrations/plugin.js +9 -6
  250. package/dist/localization/default-messages.d.ts +54 -0
  251. package/dist/localization/default-messages.js +57 -2
  252. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  253. package/dist/mcp-client/oauth-routes.d.ts +2 -0
  254. package/dist/mcp-client/oauth-routes.js +73 -6
  255. package/dist/notifications/routes.d.ts +1 -1
  256. package/dist/observability/routes.d.ts +3 -3
  257. package/dist/onboarding/app-profile.js +11 -0
  258. package/dist/resources/handlers.d.ts +1 -1
  259. package/dist/resources/store.js +15 -1
  260. package/dist/scripts/call-agent.js +34 -4
  261. package/dist/secrets/routes.d.ts +9 -9
  262. package/dist/server/agent-chat/action-filters-a2a.d.ts +1 -1
  263. package/dist/server/agent-chat/action-filters-a2a.js +36 -1
  264. package/dist/server/agent-chat-plugin.js +29 -8
  265. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  266. package/dist/server/auth.js +47 -11
  267. package/dist/server/better-auth-instance.js +5 -3
  268. package/dist/server/core-routes-plugin.d.ts +8 -1
  269. package/dist/server/core-routes-plugin.js +28 -3
  270. package/dist/server/credential-provider.d.ts +3 -3
  271. package/dist/server/credential-provider.js +90 -4
  272. package/dist/server/onboarding-html.d.ts +2 -0
  273. package/dist/server/onboarding-html.js +115 -29
  274. package/dist/server/self-dispatch.d.ts +5 -4
  275. package/dist/server/self-dispatch.js +25 -14
  276. package/dist/server/ssr-handler.d.ts +1 -1
  277. package/dist/server/ssr-handler.js +1 -1
  278. package/dist/server/transcribe-voice.d.ts +1 -1
  279. package/dist/shared/cache-control.d.ts +26 -2
  280. package/dist/shared/cache-control.js +43 -2
  281. package/dist/shared/password-policy.d.ts +3 -0
  282. package/dist/shared/password-policy.js +3 -0
  283. package/dist/sharing/actions/set-resource-visibility.js +16 -4
  284. package/dist/sharing/registry.d.ts +14 -0
  285. package/dist/styles/agent-native.css +6 -0
  286. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
  287. package/dist/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  288. package/dist/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
  289. package/dist/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  290. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +296 -0
  291. package/dist/templates/default/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  292. package/dist/templates/default/.agents/skills/turn-into-skill/SKILL.md +112 -0
  293. package/dist/templates/default/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  294. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +296 -0
  295. package/dist/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  296. package/dist/templates/headless/.agents/skills/turn-into-skill/SKILL.md +112 -0
  297. package/dist/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  298. package/dist/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +6 -0
  299. package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +51 -0
  300. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +296 -0
  301. package/dist/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  302. package/dist/templates/workspace-core/.agents/skills/turn-into-skill/SKILL.md +112 -0
  303. package/dist/templates/workspace-core/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  304. package/dist/templates/workspace-root/AGENTS.md +7 -0
  305. package/dist/user-profile/actions/change-password.js +8 -3
  306. package/dist/user-profile/actions/set-password.js +5 -1
  307. package/dist/vite/action-types-plugin.js +59 -0
  308. package/docs/content/skills-guide.mdx +13 -0
  309. package/package.json +6 -2
  310. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
  311. package/src/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  312. package/src/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
  313. package/src/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  314. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +296 -0
  315. package/src/templates/default/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  316. package/src/templates/default/.agents/skills/turn-into-skill/SKILL.md +112 -0
  317. package/src/templates/default/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  318. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +296 -0
  319. package/src/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  320. package/src/templates/headless/.agents/skills/turn-into-skill/SKILL.md +112 -0
  321. package/src/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  322. package/src/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +6 -0
  323. package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +51 -0
  324. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +296 -0
  325. package/src/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  326. package/src/templates/workspace-core/.agents/skills/turn-into-skill/SKILL.md +112 -0
  327. package/src/templates/workspace-core/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  328. package/src/templates/workspace-root/AGENTS.md +7 -0
@@ -1,4 +1,9 @@
1
- import type { ParsedSlide, ParsedTextRun } from "./pptx-parser.js";
1
+ import type {
2
+ ParsedElement,
3
+ ParsedParagraph,
4
+ ParsedSlide,
5
+ ParsedTextRun,
6
+ } from "./pptx-parser.js";
2
7
 
3
8
  /** Escape HTML special characters. */
4
9
  function esc(text: string): string {
@@ -47,6 +52,13 @@ export function buildFullBleedImageSlideHtml(
47
52
  </div>`;
48
53
  }
49
54
 
55
+ /** Render a rasterized source page without changing its layout or text. */
56
+ export function buildFullPageImageSlideHtml(imageUrl: string): string {
57
+ return `<div class="fmd-slide" style="position: relative; width: 100%; height: 100%; overflow: hidden; background: hsl(var(--background));">
58
+ <img src="${esc(imageUrl)}" alt="" style="position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain;" />
59
+ </div>`;
60
+ }
61
+
50
62
  /** Wrap text in formatting tags based on run properties. */
51
63
  function formatRun(run: ParsedTextRun): string {
52
64
  let text = esc(run.content);
@@ -63,7 +75,11 @@ const DEFAULT_IMPORT_FONT = "'Poppins', sans-serif";
63
75
  function cssFontFamily(themeFont: string | undefined): string {
64
76
  if (!themeFont) return DEFAULT_IMPORT_FONT;
65
77
  const safeName = themeFont.replace(/["']/g, "").trim();
66
- return safeName ? `'${safeName}', sans-serif` : DEFAULT_IMPORT_FONT;
78
+ if (!safeName) return DEFAULT_IMPORT_FONT;
79
+ if (safeName.toLowerCase().startsWith("poppins")) {
80
+ return "'Poppins', sans-serif";
81
+ }
82
+ return `'${safeName}', sans-serif`;
67
83
  }
68
84
 
69
85
  /**
@@ -107,9 +123,13 @@ function groupIntoParagraphs(texts: ParsedTextRun[]): ParsedTextRun[][] {
107
123
  */
108
124
  export function convertToSlideHtml(
109
125
  slide: ParsedSlide,
110
- imageUrl?: string,
126
+ imageUrls?: string | Record<string, string>,
111
127
  themeFont?: string,
112
128
  ): string {
129
+ if (slide.elements?.length) {
130
+ return buildFidelitySlide(slide, imageUrls, themeFont);
131
+ }
132
+
113
133
  const paragraphs = groupIntoParagraphs(slide.texts);
114
134
  const fontFamily = cssFontFamily(themeFont);
115
135
 
@@ -117,7 +137,12 @@ export function convertToSlideHtml(
117
137
  // has no room to show it, which is how imports used to silently drop
118
138
  // photos from otherwise short/title-shaped slides.
119
139
  if (slide.images.length > 0) {
120
- return buildImageSlide(paragraphs, slide, imageUrl, fontFamily);
140
+ return buildImageSlide(
141
+ paragraphs,
142
+ slide,
143
+ typeof imageUrls === "string" ? imageUrls : undefined,
144
+ fontFamily,
145
+ );
121
146
  }
122
147
 
123
148
  if (slide.layoutHint === "title" || paragraphs.length <= 2) {
@@ -127,6 +152,194 @@ export function convertToSlideHtml(
127
152
  return buildContentSlide(paragraphs, slide, fontFamily);
128
153
  }
129
154
 
155
+ const DEFAULT_SLIDE_WIDTH_EMU = 9144000;
156
+ const DEFAULT_SLIDE_HEIGHT_EMU = 5143500;
157
+ const CSS_PX_PER_POINT = 96 / 72;
158
+ const DEFAULT_PPTX_BACKGROUND = "#000000"; // guard:allow-raw-color - preserve PPTX black when no background is declared
159
+ const DEFAULT_PPTX_FOREGROUND = "#ffffff"; // guard:allow-raw-color - preserve PPTX white when no run color is declared
160
+
161
+ function buildFidelitySlide(
162
+ slide: ParsedSlide,
163
+ imageUrls: string | Record<string, string> | undefined,
164
+ themeFont: string | undefined,
165
+ ): string {
166
+ const widthEmu = slide.widthEmu || DEFAULT_SLIDE_WIDTH_EMU;
167
+ const heightEmu = slide.heightEmu || DEFAULT_SLIDE_HEIGHT_EMU;
168
+ const background = slide.backgroundColor ?? DEFAULT_PPTX_BACKGROUND;
169
+ const gridStyle = slide.backgroundGrid
170
+ ? `background-image:linear-gradient(to right, ${esc(slide.backgroundGrid.color)} 0 ${Math.max(0.5, toSlidePxX(slide.backgroundGrid.lineWidthEmu, widthEmu))}px, transparent ${Math.max(0.5, toSlidePxX(slide.backgroundGrid.lineWidthEmu, widthEmu))}px),linear-gradient(to bottom, ${esc(slide.backgroundGrid.color)} 0 ${Math.max(0.5, toSlidePxY(slide.backgroundGrid.lineWidthEmu, heightEmu))}px, transparent ${Math.max(0.5, toSlidePxY(slide.backgroundGrid.lineWidthEmu, heightEmu))}px);background-size:${toSlidePxX(slide.backgroundGrid.stepXEmu, widthEmu)}px ${toSlidePxY(slide.backgroundGrid.stepYEmu, heightEmu)}px;background-position:${toSlidePxX(slide.backgroundGrid.offsetXEmu, widthEmu)}px ${toSlidePxY(slide.backgroundGrid.offsetYEmu, heightEmu)}px;background-repeat:repeat;`
171
+ : "";
172
+ const elements = slide.elements ?? [];
173
+ const html = elements
174
+ .map((element, index) =>
175
+ buildFidelityElement(
176
+ element,
177
+ index,
178
+ widthEmu,
179
+ heightEmu,
180
+ imageUrls,
181
+ themeFont,
182
+ ),
183
+ )
184
+ .join("\n");
185
+
186
+ return `<div class="fmd-slide fmd-imported-pptx" data-imported-pptx="true" data-slide-width-emu="${widthEmu}" data-slide-height-emu="${heightEmu}" style="position: relative; width: 100%; height: 100%; overflow: hidden; background: ${esc(background)};${gridStyle} font-family: ${cssFontFamily(themeFont)};">${html}
187
+ </div>`;
188
+ }
189
+
190
+ function buildFidelityElement(
191
+ element: ParsedElement,
192
+ index: number,
193
+ widthEmu: number,
194
+ heightEmu: number,
195
+ imageUrls: string | Record<string, string> | undefined,
196
+ themeFont: string | undefined,
197
+ ): string {
198
+ const position = `position: absolute; left: ${toSlidePxX(element.x, widthEmu)}px; top: ${toSlidePxY(element.y, heightEmu)}px; width: ${toSlidePxX(element.width, widthEmu)}px; height: ${toSlidePxY(element.height, heightEmu)}px; z-index: ${index}; box-sizing: border-box;`;
199
+ const rotation = element.rotation
200
+ ? ` transform: rotate(${element.rotation}deg); transform-origin: center center;`
201
+ : "";
202
+ const objectId = ` data-slide-object-id="${esc(element.id)}"`;
203
+
204
+ if (element.kind === "image") {
205
+ const url = imageUrlForElement(element, imageUrls);
206
+ const imageStyle = imageRenderStyle(element);
207
+ return `<div class="fmd-pptx-image" data-pptx-element-kind="image" data-pptx-image-name="${esc(element.image?.name ?? "image")}"${objectId} style="${position}${rotation} overflow: hidden;">${url ? `<img src="${esc(url)}" alt="" style="${imageStyle}" />` : `<div class="fmd-img-placeholder" style="width:100%;height:100%;">Imported image: ${esc(element.image?.name ?? "image")}</div>`}</div>`;
208
+ }
209
+
210
+ const decoration = shapeDecoration(element, widthEmu);
211
+ if (element.kind === "shape") {
212
+ return `<div class="fmd-pptx-shape" data-pptx-element-kind="shape"${objectId} style="${position}${rotation}${decoration}"></div>`;
213
+ }
214
+
215
+ const textStyle = textBoxStyle(element, widthEmu, heightEmu, themeFont);
216
+ const defaultFontWeight = element.placeholderType === "title" ? 700 : 400;
217
+ const paragraphs = (element.paragraphs ?? [])
218
+ .map((paragraph, paragraphIndex) =>
219
+ buildFidelityParagraph(
220
+ paragraph,
221
+ paragraphIndex,
222
+ widthEmu,
223
+ themeFont,
224
+ defaultFontWeight,
225
+ ),
226
+ )
227
+ .join("\n");
228
+ return `<div class="fmd-pptx-text" data-pptx-element-kind="text"${objectId} style="${position}${rotation}${decoration}${textStyle}">${paragraphs}</div>`;
229
+ }
230
+
231
+ function toSlidePxX(valueEmu: number, slideWidthEmu: number): number {
232
+ return Math.round((valueEmu / slideWidthEmu) * 960 * 1000) / 1000;
233
+ }
234
+
235
+ function toSlidePxY(valueEmu: number, slideHeightEmu: number): number {
236
+ return Math.round((valueEmu / slideHeightEmu) * 540 * 1000) / 1000;
237
+ }
238
+
239
+ function imageUrlForElement(
240
+ element: ParsedElement,
241
+ imageUrls: string | Record<string, string> | undefined,
242
+ ): string | undefined {
243
+ if (typeof imageUrls === "string") return imageUrls;
244
+ return imageUrls?.[element.id];
245
+ }
246
+
247
+ function imageRenderStyle(element: ParsedElement): string {
248
+ const crop = element.image?.crop;
249
+ if (!crop) return "display:block;width:100%;height:100%;object-fit:fill;";
250
+ const visibleWidth = Math.max(0.001, 1 - crop.left - crop.right);
251
+ const visibleHeight = Math.max(0.001, 1 - crop.top - crop.bottom);
252
+ return `display:block;position:absolute;left:${(-crop.left / visibleWidth) * 100}%;top:${(-crop.top / visibleHeight) * 100}%;width:${(1 / visibleWidth) * 100}%;height:${(1 / visibleHeight) * 100}%;object-fit:fill;`;
253
+ }
254
+
255
+ function shapeDecoration(element: ParsedElement, widthEmu: number): string {
256
+ const fill = element.fill ? `background: ${esc(element.fill)};` : "";
257
+ const line = element.lineColor
258
+ ? `border: ${Math.max(1, toSlidePxX(element.lineWidth ?? 12700, widthEmu))}px solid ${esc(element.lineColor)};`
259
+ : "";
260
+ const radius = element.shapeType === "roundRect" ? "border-radius: 6px;" : "";
261
+ return `${fill}${line}${radius}`;
262
+ }
263
+
264
+ function textBoxStyle(
265
+ element: ParsedElement,
266
+ widthEmu: number,
267
+ heightEmu: number,
268
+ themeFont: string | undefined,
269
+ ): string {
270
+ const padding = element.padding;
271
+ const left = padding ? toSlidePxX(padding.left, widthEmu) : 0;
272
+ const right = padding ? toSlidePxX(padding.right, widthEmu) : 0;
273
+ const top = padding ? toSlidePxY(padding.top, heightEmu) : 0;
274
+ const bottom = padding ? toSlidePxY(padding.bottom, heightEmu) : 0;
275
+ const align = element.paragraphs?.[0]?.alignment ?? "left";
276
+ const vertical =
277
+ element.verticalAlign === "middle"
278
+ ? "justify-content:center;"
279
+ : element.verticalAlign === "bottom"
280
+ ? "justify-content:flex-end;"
281
+ : "justify-content:flex-start;";
282
+ return `display:flex;flex-direction:column;${vertical}padding:${top}px ${right}px ${bottom}px ${left}px;font-family:${cssFontFamily(themeFont)};text-align:${align};overflow:visible;`;
283
+ }
284
+
285
+ function buildFidelityParagraph(
286
+ paragraph: ParsedParagraph,
287
+ paragraphIndex: number,
288
+ widthEmu: number,
289
+ themeFont: string | undefined,
290
+ defaultFontWeight: number,
291
+ ): string {
292
+ const firstRun = paragraph.runs[0];
293
+ const fontSize = (firstRun?.fontSize ?? 18) * CSS_PX_PER_POINT;
294
+ const lineHeight = paragraph.lineSpacing ?? 1.2;
295
+ const bullet = paragraph.bulletChar
296
+ ? `<span aria-hidden="true" style="display:inline-block;width:${fontSize * 0.75}px;min-width:${fontSize * 0.75}px;margin-right:${fontSize * 0.65}px;color:${esc(paragraph.bulletColor ?? firstRun?.color ?? DEFAULT_PPTX_FOREGROUND)};font-family:${cssFontFamily(paragraph.bulletFontFamily ?? themeFont)};font-size:${(paragraph.bulletSize ?? firstRun?.fontSize ?? 18) * CSS_PX_PER_POINT}px;">${esc(paragraph.bulletChar)}</span>`
297
+ : "";
298
+ const marginLeft = paragraph.marginLeftEmu
299
+ ? toSlidePxX(paragraph.marginLeftEmu, widthEmu)
300
+ : 0;
301
+ const indent = paragraph.indentEmu
302
+ ? toSlidePxX(paragraph.indentEmu, widthEmu)
303
+ : 0;
304
+ const spacingBefore = paragraph.spaceBeforePt ?? 0;
305
+ const spacingAfter = paragraph.spaceAfterPt ?? 0;
306
+ const bulletMargin = paragraph.bulletChar ? `margin-left:${indent}px;` : "";
307
+ const text = paragraph.runs
308
+ .map((run) => formatFidelityRun(run, themeFont, defaultFontWeight))
309
+ .join("");
310
+ return `<p data-pptx-paragraph="${paragraphIndex}" style="display:block;flex:0 0 auto;text-align:${paragraph.alignment ?? "left"};white-space:pre-wrap;margin:${spacingBefore * CSS_PX_PER_POINT}px 0 ${spacingAfter * CSS_PX_PER_POINT}px;line-height:${lineHeight};font-size:${fontSize}px;min-height:${fontSize * lineHeight}px;padding-left:${marginLeft}px;text-indent:${paragraph.bulletChar ? 0 : indent}px;">${bullet.replace("display:inline-block;", `display:inline-block;${bulletMargin}`)}${text}</p>`;
311
+ }
312
+
313
+ function formatFidelityRun(
314
+ run: ParsedTextRun,
315
+ themeFont: string | undefined,
316
+ defaultFontWeight = 400,
317
+ ): string {
318
+ const styles = [
319
+ `font-size:${(run.fontSize ?? 18) * CSS_PX_PER_POINT}px`,
320
+ `font-family:${cssFontFamily(run.fontFamily ?? themeFont)}`,
321
+ `color:${esc(run.color ?? DEFAULT_PPTX_FOREGROUND)}`,
322
+ `font-weight:${run.bold ? 700 : fontWeightForFamily(run.fontFamily, defaultFontWeight)}`,
323
+ `font-style:${run.italic ? "italic" : "normal"}`,
324
+ `text-decoration:${run.underline ? "underline" : "none"}`,
325
+ ].join(";");
326
+ return `<span style="${styles};">${esc(run.content)}</span>`;
327
+ }
328
+
329
+ function fontWeightForFamily(
330
+ fontFamily: string | undefined,
331
+ fallback: number,
332
+ ): number {
333
+ const normalized = fontFamily?.toLowerCase() ?? "";
334
+ if (!normalized) return fallback;
335
+ if (/(?:semi|demi)bold|semibold/.test(normalized)) return 600;
336
+ if (/extra[- ]?bold|heavy/.test(normalized)) return 800;
337
+ if (/bold/.test(normalized)) return 700;
338
+ if (/medium/.test(normalized)) return 500;
339
+ if (/light|thin/.test(normalized)) return 300;
340
+ return 400;
341
+ }
342
+
130
343
  function buildTitleSlide(
131
344
  paragraphs: ParsedTextRun[][],
132
345
  slide: ParsedSlide,
@@ -166,7 +379,7 @@ function buildContentSlide(
166
379
  })
167
380
  .join("\n");
168
381
 
169
- bulletsHtml = `\n <div style="display: flex; flex-direction: column; gap: 20px;">
382
+ bulletsHtml = `\n <div class="fmd-animation-container" style="display: flex; flex-direction: column; gap: 20px;">
170
383
  ${bulletItems}
171
384
  </div>`;
172
385
  }
@@ -232,15 +445,20 @@ function buildOverlayImageSlide(
232
445
  const headingHtml = headingPara.map(formatRun).join(" ") || "Slide";
233
446
 
234
447
  const captionParas = paragraphs.slice(1);
235
- const captionHtml = captionParas
236
- .map((para) => para.map(formatRun).join(" "))
237
- .join(" ");
448
+ const captionHtml = captionParas.length
449
+ ? `<div class="fmd-animation-container" style="display: flex; flex-direction: column; gap: 8px;">${captionParas
450
+ .map(
451
+ (para) =>
452
+ `<p style="font-size: 18px; color: rgba(255,255,255,0.75); /* guard:allow-raw-color - standalone imported slide HTML uses fixed contrast colors */ line-height: 1.5; margin: 0;">${para.map(formatRun).join(" ")}</p>`,
453
+ )
454
+ .join("\n")}</div>`
455
+ : "";
238
456
 
239
457
  return `<div class="fmd-slide" style="position: relative; width: 100%; height: 100%; overflow: hidden;">
240
458
  <img src="${esc(imageUrl)}" alt="" style="position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;" />
241
459
  <div style="position: absolute; inset: 0; background: linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.35) 55%, rgba(0,0,0,0) 80%);"></div>
242
460
  <div style="position: absolute; left: 0; right: 0; bottom: 0; padding: 56px 70px; font-family: ${fontFamily};">
243
- <h2 style="font-size: 40px; font-weight: 900; color: #fff; line-height: 1.15; letter-spacing: -1px; margin: 0 0 ${captionHtml ? "12px" : "0"} 0;">${headingHtml}</h2>${captionHtml ? `\n <p style="font-size: 18px; color: rgba(255,255,255,0.75); line-height: 1.5; margin: 0;">${captionHtml}</p>` : ""}
461
+ <h2 style="font-size: 40px; font-weight: 900; color: #fff; /* guard:allow-raw-color - standalone imported slide HTML uses fixed contrast colors */ line-height: 1.15; letter-spacing: -1px; margin: 0 0 ${captionHtml ? "12px" : "0"} 0;">${headingHtml}</h2>${captionHtml ? `\n ${captionHtml}` : ""}
244
462
  </div>
245
463
  </div>`;
246
464
  }
@@ -256,9 +474,14 @@ function buildStackedImageSlide(
256
474
  const headingText = headingPara.map(formatRun).join(" ") || "Slide";
257
475
 
258
476
  const captionParas = paragraphs.slice(1);
259
- const captionText = captionParas
260
- .map((para) => para.map(formatRun).join(" "))
261
- .join(" ");
477
+ const captionText = captionParas.length
478
+ ? `<div class="fmd-animation-container" style="display: flex; flex-direction: column; gap: 8px;">${captionParas
479
+ .map(
480
+ (para) =>
481
+ `<p style="font-size: 16px; color: rgba(255,255,255,0.7); /* guard:allow-raw-color - standalone imported slide HTML uses fixed contrast colors */ line-height: 1.5; margin: 0;">${para.map(formatRun).join(" ")}</p>`,
482
+ )
483
+ .join("\n")}</div>`
484
+ : "";
262
485
 
263
486
  const imageName = slide.images[0]?.name ?? "image";
264
487
  // Size the box to the image's own placed aspect ratio instead of a fixed
@@ -278,7 +501,7 @@ function buildStackedImageSlide(
278
501
 
279
502
  return `<div class="fmd-slide" style="padding: 64px 90px; display: flex; flex-direction: column; justify-content: flex-start; font-family: ${fontFamily};">
280
503
  ${imageHtml}
281
- <h2 style="font-size: 32px; font-weight: 900; color: #fff; line-height: 1.2; letter-spacing: -0.5px; margin: 0 0 12px 0;">${headingText}</h2>${captionText ? `\n <p style="font-size: 16px; color: rgba(255,255,255,0.7); line-height: 1.5; margin: 0;">${captionText}</p>` : ""}
504
+ <h2 style="font-size: 32px; font-weight: 900; color: #fff; /* guard:allow-raw-color - standalone imported slide HTML uses fixed contrast colors */ line-height: 1.2; letter-spacing: -0.5px; margin: 0 0 12px 0;">${headingText}</h2>${captionText ? `\n ${captionText}` : ""}
282
505
  </div>`;
283
506
  }
284
507
 
@@ -0,0 +1,67 @@
1
+ import { uploadFile } from "@agent-native/core/file-upload";
2
+
3
+ import { storeLocalImportedAsset } from "../../lib/import-asset-storage.js";
4
+ import type { ParsedElement, ParsedSlide } from "./pptx-parser.js";
5
+
6
+ const BROWSER_RENDERABLE_IMAGE_MIME_TYPES = new Set([
7
+ "image/png",
8
+ "image/jpeg",
9
+ "image/gif",
10
+ "image/webp",
11
+ "image/svg+xml",
12
+ "image/bmp",
13
+ ]);
14
+
15
+ export async function uploadPptxSlideImages(args: {
16
+ slide: ParsedSlide;
17
+ slideIndex: number;
18
+ ownerEmail: string;
19
+ }): Promise<{ urls: Record<string, string>; imageSkippedCount: number }> {
20
+ const imageElements = (args.slide.elements ?? []).filter(
21
+ (
22
+ element,
23
+ ): element is ParsedElement & {
24
+ kind: "image";
25
+ image: NonNullable<ParsedElement["image"]>;
26
+ } => element.kind === "image" && Boolean(element.image),
27
+ );
28
+ const urls: Record<string, string> = {};
29
+
30
+ for (const [imageIndex, element] of imageElements.entries()) {
31
+ const image = element.image;
32
+ if (!BROWSER_RENDERABLE_IMAGE_MIME_TYPES.has(image.mimeType)) continue;
33
+ const filename = `pptx-import-${Date.now()}-s${args.slideIndex}-i${imageIndex}-${image.name}`;
34
+ let url: string | undefined;
35
+ try {
36
+ const result = await uploadFile({
37
+ data: Buffer.from(image.data),
38
+ filename,
39
+ mimeType: image.mimeType,
40
+ ownerEmail: args.ownerEmail,
41
+ recordAsset: false,
42
+ });
43
+ url = result?.url;
44
+ } catch {
45
+ url = undefined;
46
+ }
47
+
48
+ if (!url) {
49
+ url =
50
+ (await storeLocalImportedAsset({
51
+ email: args.ownerEmail,
52
+ filename,
53
+ mimeType: image.mimeType,
54
+ data: image.data,
55
+ })) ?? undefined;
56
+ }
57
+ if (url) urls[element.id] = url;
58
+ }
59
+
60
+ return {
61
+ urls,
62
+ imageSkippedCount: Math.max(
63
+ 0,
64
+ imageElements.length - Object.keys(urls).length,
65
+ ),
66
+ };
67
+ }
@@ -1,5 +1,7 @@
1
1
  export {
2
2
  parsePptxPresentation as parsePptx,
3
+ type ParsedPptxElement as ParsedElement,
4
+ type ParsedPptxParagraph as ParsedParagraph,
3
5
  type ParsedPptxImage as ParsedImage,
4
6
  type ParsedPptxPresentation as ParsedPresentation,
5
7
  type ParsedPptxSlide as ParsedSlide,
@@ -9,9 +9,11 @@ import {
9
9
  import { nanoid } from "nanoid";
10
10
 
11
11
  import {
12
+ MAX_FIG_REFERENCE_FILE_BYTES,
13
+ MAX_REFERENCE_FILE_BYTES,
12
14
  SLIDES_REFERENCE_FILE_ERROR_LABEL,
13
15
  isSlidesReferenceFileExtension,
14
- } from "../../shared/upload-types";
16
+ } from "../../shared/upload-types.js";
15
17
  import { tenantUploadDir } from "../lib/tenant-files.js";
16
18
  import {
17
19
  isHostedSlidesRuntime,
@@ -23,8 +25,10 @@ import {
23
25
  withSlidesRequestContext,
24
26
  } from "./request-auth-context.js";
25
27
 
26
- export const MAX_REFERENCE_FILE_BYTES = 50 * 1024 * 1024;
27
- export const MAX_FIG_REFERENCE_FILE_BYTES = 200 * 1024 * 1024;
28
+ export {
29
+ MAX_FIG_REFERENCE_FILE_BYTES,
30
+ MAX_REFERENCE_FILE_BYTES,
31
+ } from "../../shared/upload-types.js";
28
32
  const FIG_LOCAL_COPY_SIGNATURE = new Uint8Array([
29
33
  0x66, 0x69, 0x67, 0x2d, 0x6b, 0x69, 0x77, 0x69,
30
34
  ]);
@@ -2,10 +2,13 @@ import path from "path";
2
2
 
3
3
  import type { AgentChatAttachment } from "@agent-native/core/server";
4
4
 
5
- import { isSlidesReferenceFileExtension } from "../../shared/upload-types.js";
5
+ import {
6
+ isSlidesReferenceFileExtension,
7
+ MAX_REFERENCE_FILE_BYTES,
8
+ } from "../../shared/upload-types.js";
6
9
  import { saveUploadedReferenceFile } from "../handlers/uploads.js";
7
10
 
8
- const MAX_CHAT_UPLOAD_BYTES = 50 * 1024 * 1024;
11
+ const MAX_CHAT_UPLOAD_BYTES = MAX_REFERENCE_FILE_BYTES;
9
12
  const MAX_INLINE_IMAGE_BYTES = 10 * 1024 * 1024;
10
13
 
11
14
  function decodeDataUrl(data: string | undefined): {
@@ -117,7 +120,7 @@ export async function prepareSlidesChatAttachments(args: {
117
120
  "File handling rules:",
118
121
  "- If the request refers to the current or visible deck, call `view-screen` first to confirm the active deckId, then pass that deckId to import or slide-edit actions.",
119
122
  '- PPTX files: call `import-pptx --filePath "<path>" --deckId <deckId>` when updating the visible deck, or omit deckId only when the user explicitly wants a new deck.',
120
- '- PDF and DOCX files: call `import-file --filePath "<path>" --format auto --deckId <deckId>` and use the returned extracted text as source material before creating slides. The returned text is capped for reliability; re-run with maxChars only if more context is needed. If the user wants a direct replacement import, pass `--importIntoDeck true` as well.',
123
+ '- PDF and DOCX files: call `import-file --filePath "<path>" --format auto --deckId <deckId>` and use the returned extracted text as source material before creating editable slides. For a visual PDF that the user wants preserved, beautified, or restyled from its original layout, pass `--importIntoDeck true` so each page is imported source-faithfully first; the source text is persisted in slide notes for later inspection.',
121
124
  '- Figma `.fig` files: call `import-file --filePath "<path>" --format fig` to start Builder design-system indexing. Do not create a local design system directly from the upload.',
122
125
  "- For deck-generation requests, start mutating promptly: create or update the first slide as soon as source material is extracted, then continue slide-by-slide with add-slide/update-slide.",
123
126
  '- Image files with an embeddable URL can be inserted directly into slide HTML as `<img src="...">` or used as visual references.',
@@ -5,7 +5,10 @@
5
5
  * and preview them without the app having sent anything yet.
6
6
  */
7
7
 
8
- import { defineTransactionalEmail } from "@agent-native/core/email-catalog";
8
+ import {
9
+ defineTransactionalEmail,
10
+ getTransactionalEmail,
11
+ } from "@agent-native/core/email-catalog";
9
12
 
10
13
  import { renderDeckCommentEmail } from "./comment-notifications.js";
11
14
 
@@ -14,7 +17,9 @@ export const SLIDES_DECK_COMMENT_EMAIL_ID = "slides.deck-comment";
14
17
  let registered = false;
15
18
 
16
19
  export function registerSlidesEmails(): void {
17
- if (registered) return;
20
+ // Nitro can re-evaluate this module during dev HMR while the core registry
21
+ // remains alive. Treat the existing Slides-owned entry as already registered.
22
+ if (registered || getTransactionalEmail(SLIDES_DECK_COMMENT_EMAIL_ID)) return;
18
23
  registered = true;
19
24
 
20
25
  defineTransactionalEmail({
@@ -0,0 +1,50 @@
1
+ export function formatGoogleOAuthError(error: unknown): string {
2
+ const rawMessage =
3
+ error instanceof Error ? error.message : String(error || "Unknown error");
4
+ const message = rawMessage.replace(/\s+/g, " ").trim();
5
+ const normalized = message.toLowerCase();
6
+
7
+ if (
8
+ normalized.includes("insufficient permission") ||
9
+ normalized.includes("insufficient_scope")
10
+ ) {
11
+ return "Google Docs access was denied. Connect again and approve the requested Google Drive file permission.";
12
+ }
13
+ if (
14
+ normalized.includes("redirect_uri_mismatch") ||
15
+ normalized.includes("redirect uri")
16
+ ) {
17
+ return "Google rejected the redirect URI. Add the current Slides callback URL to the Google OAuth client and try again.";
18
+ }
19
+ if (
20
+ normalized.includes("access_denied") ||
21
+ normalized.includes("user denied") ||
22
+ normalized === "denied"
23
+ ) {
24
+ return "Google access was canceled or denied. Connect Google again and approve access.";
25
+ }
26
+ if (
27
+ normalized.includes("invalid_grant") ||
28
+ normalized.includes("connection expired") ||
29
+ normalized.includes("token has been expired")
30
+ ) {
31
+ return "The Google connection expired. Connect Google again.";
32
+ }
33
+ if (
34
+ normalized.includes("invalid_client") ||
35
+ normalized.includes("unauthorized_client")
36
+ ) {
37
+ return "Google rejected the OAuth client configuration. Check the Slides Google OAuth credentials and authorized redirect URI.";
38
+ }
39
+ if (
40
+ normalized.includes("unverified") ||
41
+ normalized.includes("not verified") ||
42
+ normalized.includes("test user") ||
43
+ normalized.includes("sensitive app") ||
44
+ normalized.includes("access blocked")
45
+ ) {
46
+ return "Google blocked this app because its OAuth consent screen is unverified or this account is not an allowed test user.";
47
+ }
48
+
49
+ return message.slice(0, 240) || "Google OAuth failed. Try connecting again.";
50
+ }
@@ -17,6 +17,7 @@ const USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo";
17
17
  export const GOOGLE_DOCS_PROVIDER = "google-docs";
18
18
  export const GOOGLE_DOCS_SCOPES = [
19
19
  "https://www.googleapis.com/auth/drive.file",
20
+ "https://www.googleapis.com/auth/presentations.readonly",
20
21
  "https://www.googleapis.com/auth/userinfo.email",
21
22
  "https://www.googleapis.com/auth/userinfo.profile",
22
23
  ];
@@ -0,0 +1,127 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ import {
6
+ decryptSecretValue,
7
+ encryptSecretValue,
8
+ } from "@agent-native/core/secrets/crypto";
9
+ import { getConfiguredAppBasePath } from "@agent-native/core/server";
10
+
11
+ import { isHostedSlidesRuntime, tenantFileKey } from "./tenant-files.js";
12
+
13
+ const IMPORT_ASSET_PREFIX = "slides-import-asset:v1:";
14
+
15
+ export interface LocalImportedAssetDescriptor {
16
+ kind: "slides-import-asset";
17
+ version: 1;
18
+ ownerKey: string;
19
+ id: string;
20
+ filename: string;
21
+ mimeType: string;
22
+ }
23
+
24
+ export function isLocalImportAssetFallbackAvailable(): boolean {
25
+ return !isHostedSlidesRuntime() && process.env.NODE_ENV !== "production";
26
+ }
27
+
28
+ export async function storeLocalImportedAsset(args: {
29
+ email: string;
30
+ filename: string;
31
+ mimeType: string;
32
+ data: Uint8Array;
33
+ }): Promise<string | null> {
34
+ if (!isLocalImportAssetFallbackAvailable()) return null;
35
+
36
+ const id = randomUUID();
37
+ const safeFilename = path
38
+ .basename(args.filename)
39
+ .replace(/[^a-zA-Z0-9_.-]/g, "-");
40
+ const directory = path.join(
41
+ process.cwd(),
42
+ "data",
43
+ "import-assets",
44
+ tenantFileKey(args.email),
45
+ );
46
+ await mkdir(directory, { recursive: true });
47
+ await writeFile(path.join(directory, `${id}-${safeFilename}`), args.data);
48
+
49
+ const descriptor: LocalImportedAssetDescriptor = {
50
+ kind: "slides-import-asset",
51
+ version: 1,
52
+ ownerKey: tenantFileKey(args.email),
53
+ id,
54
+ filename: safeFilename,
55
+ mimeType: args.mimeType,
56
+ };
57
+ const token = `${IMPORT_ASSET_PREFIX}${encryptSecretValue(
58
+ JSON.stringify(descriptor),
59
+ )}`;
60
+ return `${getConfiguredAppBasePath()}/api/import-assets/${encodeURIComponent(token)}`;
61
+ }
62
+
63
+ export function decodeLocalImportedAssetToken(
64
+ token: string,
65
+ ): LocalImportedAssetDescriptor {
66
+ if (!token.startsWith(IMPORT_ASSET_PREFIX)) {
67
+ throw new Error("Invalid imported asset token");
68
+ }
69
+ const descriptor = JSON.parse(
70
+ decryptSecretValue(token.slice(IMPORT_ASSET_PREFIX.length)),
71
+ ) as LocalImportedAssetDescriptor;
72
+ if (
73
+ descriptor?.kind !== "slides-import-asset" ||
74
+ descriptor.version !== 1 ||
75
+ typeof descriptor.ownerKey !== "string" ||
76
+ !/^[a-f0-9]{24}$/.test(descriptor.ownerKey) ||
77
+ typeof descriptor.id !== "string" ||
78
+ !/^[0-9a-f-]{36}$/.test(descriptor.id) ||
79
+ typeof descriptor.filename !== "string" ||
80
+ descriptor.filename !== path.basename(descriptor.filename) ||
81
+ typeof descriptor.mimeType !== "string"
82
+ ) {
83
+ throw new Error("Invalid imported asset descriptor");
84
+ }
85
+ return descriptor;
86
+ }
87
+
88
+ /** Read a local development import asset after rechecking its owner scope. */
89
+ export async function readLocalImportedAsset(args: {
90
+ token: string;
91
+ email: string;
92
+ }): Promise<{ data: Uint8Array; mimeType: string } | null> {
93
+ if (!isLocalImportAssetFallbackAvailable()) return null;
94
+
95
+ let descriptor: LocalImportedAssetDescriptor;
96
+ try {
97
+ descriptor = decodeLocalImportedAssetToken(decodeURIComponent(args.token));
98
+ } catch {
99
+ // coercion-ok: malformed tokens represent an absent local asset, not a successful read.
100
+ return null;
101
+ }
102
+ if (descriptor.ownerKey !== tenantFileKey(args.email)) return null;
103
+
104
+ const directory = path.resolve(
105
+ process.cwd(),
106
+ "data",
107
+ "import-assets",
108
+ descriptor.ownerKey,
109
+ );
110
+ const filepath = path.resolve(
111
+ directory,
112
+ `${descriptor.id}-${descriptor.filename}`,
113
+ );
114
+ if (!filepath.startsWith(directory + path.sep)) return null;
115
+
116
+ try {
117
+ return {
118
+ data: await readFile(filepath),
119
+ mimeType: descriptor.mimeType,
120
+ };
121
+ } catch {
122
+ // coercion-ok: a missing local asset is an absent export image and is skipped by the caller.
123
+ return null;
124
+ }
125
+ }
126
+
127
+ export { IMPORT_ASSET_PREFIX };