@agent-native/core 0.96.0 → 0.97.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 (450) hide show
  1. package/README.md +6 -0
  2. package/corpus/README.md +2 -2
  3. package/corpus/core/CHANGELOG.md +39 -0
  4. package/corpus/core/docs/content/audit-log.mdx +2 -1
  5. package/corpus/core/docs/content/automations.mdx +7 -7
  6. package/corpus/core/docs/content/creating-templates.mdx +1 -1
  7. package/corpus/core/docs/content/internationalization.mdx +2 -1
  8. package/corpus/core/docs/content/locales/ar-SA/audit-log.mdx +2 -1
  9. package/corpus/core/docs/content/locales/ar-SA/automations.mdx +7 -7
  10. package/corpus/core/docs/content/locales/ar-SA/getting-started.mdx +7 -1
  11. package/corpus/core/docs/content/locales/ar-SA/internationalization.mdx +1 -1
  12. package/corpus/core/docs/content/locales/ar-SA/package-lifecycle.mdx +19 -0
  13. package/corpus/core/docs/content/locales/ar-SA/sandbox-adapters.mdx +18 -7
  14. package/corpus/core/docs/content/locales/ar-SA/security.mdx +8 -0
  15. package/corpus/core/docs/content/locales/de-DE/audit-log.mdx +2 -1
  16. package/corpus/core/docs/content/locales/de-DE/automations.mdx +7 -7
  17. package/corpus/core/docs/content/locales/de-DE/getting-started.mdx +7 -1
  18. package/corpus/core/docs/content/locales/de-DE/internationalization.mdx +1 -1
  19. package/corpus/core/docs/content/locales/de-DE/package-lifecycle.mdx +22 -0
  20. package/corpus/core/docs/content/locales/de-DE/sandbox-adapters.mdx +18 -7
  21. package/corpus/core/docs/content/locales/de-DE/security.mdx +8 -0
  22. package/corpus/core/docs/content/locales/es-ES/audit-log.mdx +2 -1
  23. package/corpus/core/docs/content/locales/es-ES/automations.mdx +7 -7
  24. package/corpus/core/docs/content/locales/es-ES/getting-started.mdx +7 -1
  25. package/corpus/core/docs/content/locales/es-ES/internationalization.mdx +1 -1
  26. package/corpus/core/docs/content/locales/es-ES/package-lifecycle.mdx +22 -0
  27. package/corpus/core/docs/content/locales/es-ES/sandbox-adapters.mdx +18 -7
  28. package/corpus/core/docs/content/locales/es-ES/security.mdx +8 -0
  29. package/corpus/core/docs/content/locales/fr-FR/audit-log.mdx +2 -1
  30. package/corpus/core/docs/content/locales/fr-FR/automations.mdx +7 -7
  31. package/corpus/core/docs/content/locales/fr-FR/getting-started.mdx +7 -1
  32. package/corpus/core/docs/content/locales/fr-FR/internationalization.mdx +1 -1
  33. package/corpus/core/docs/content/locales/fr-FR/package-lifecycle.mdx +21 -0
  34. package/corpus/core/docs/content/locales/fr-FR/sandbox-adapters.mdx +19 -8
  35. package/corpus/core/docs/content/locales/fr-FR/security.mdx +8 -0
  36. package/corpus/core/docs/content/locales/hi-IN/audit-log.mdx +2 -1
  37. package/corpus/core/docs/content/locales/hi-IN/automations.mdx +7 -7
  38. package/corpus/core/docs/content/locales/hi-IN/getting-started.mdx +7 -1
  39. package/corpus/core/docs/content/locales/hi-IN/internationalization.mdx +1 -1
  40. package/corpus/core/docs/content/locales/hi-IN/package-lifecycle.mdx +20 -0
  41. package/corpus/core/docs/content/locales/hi-IN/sandbox-adapters.mdx +18 -7
  42. package/corpus/core/docs/content/locales/hi-IN/security.mdx +8 -0
  43. package/corpus/core/docs/content/locales/ja-JP/audit-log.mdx +2 -1
  44. package/corpus/core/docs/content/locales/ja-JP/automations.mdx +7 -7
  45. package/corpus/core/docs/content/locales/ja-JP/getting-started.mdx +7 -1
  46. package/corpus/core/docs/content/locales/ja-JP/internationalization.mdx +1 -1
  47. package/corpus/core/docs/content/locales/ja-JP/package-lifecycle.mdx +21 -0
  48. package/corpus/core/docs/content/locales/ja-JP/sandbox-adapters.mdx +17 -6
  49. package/corpus/core/docs/content/locales/ja-JP/security.mdx +8 -0
  50. package/corpus/core/docs/content/locales/ko-KR/audit-log.mdx +2 -1
  51. package/corpus/core/docs/content/locales/ko-KR/automations.mdx +7 -7
  52. package/corpus/core/docs/content/locales/ko-KR/getting-started.mdx +7 -1
  53. package/corpus/core/docs/content/locales/ko-KR/internationalization.mdx +1 -1
  54. package/corpus/core/docs/content/locales/ko-KR/package-lifecycle.mdx +20 -0
  55. package/corpus/core/docs/content/locales/ko-KR/sandbox-adapters.mdx +17 -6
  56. package/corpus/core/docs/content/locales/ko-KR/security.mdx +8 -0
  57. package/corpus/core/docs/content/locales/pt-BR/audit-log.mdx +2 -1
  58. package/corpus/core/docs/content/locales/pt-BR/automations.mdx +7 -7
  59. package/corpus/core/docs/content/locales/pt-BR/getting-started.mdx +7 -1
  60. package/corpus/core/docs/content/locales/pt-BR/internationalization.mdx +1 -1
  61. package/corpus/core/docs/content/locales/pt-BR/package-lifecycle.mdx +21 -0
  62. package/corpus/core/docs/content/locales/pt-BR/sandbox-adapters.mdx +18 -7
  63. package/corpus/core/docs/content/locales/pt-BR/security.mdx +8 -0
  64. package/corpus/core/docs/content/locales/zh-CN/audit-log.mdx +2 -1
  65. package/corpus/core/docs/content/locales/zh-CN/automations.mdx +7 -7
  66. package/corpus/core/docs/content/locales/zh-CN/getting-started.mdx +6 -4
  67. package/corpus/core/docs/content/locales/zh-CN/internationalization.mdx +1 -1
  68. package/corpus/core/docs/content/locales/zh-CN/package-lifecycle.mdx +19 -0
  69. package/corpus/core/docs/content/locales/zh-CN/sandbox-adapters.mdx +17 -6
  70. package/corpus/core/docs/content/locales/zh-CN/security.mdx +8 -0
  71. package/corpus/core/docs/content/locales/zh-TW/audit-log.mdx +2 -1
  72. package/corpus/core/docs/content/locales/zh-TW/automations.mdx +7 -7
  73. package/corpus/core/docs/content/locales/zh-TW/getting-started.mdx +6 -4
  74. package/corpus/core/docs/content/locales/zh-TW/internationalization.mdx +1 -1
  75. package/corpus/core/docs/content/locales/zh-TW/package-lifecycle.mdx +19 -0
  76. package/corpus/core/docs/content/locales/zh-TW/sandbox-adapters.mdx +18 -7
  77. package/corpus/core/docs/content/locales/zh-TW/security.mdx +8 -0
  78. package/corpus/core/docs/content/package-lifecycle.mdx +51 -0
  79. package/corpus/core/docs/content/sandbox-adapters.mdx +16 -5
  80. package/corpus/core/package.json +9 -2
  81. package/corpus/core/src/a2a/auth-policy.ts +33 -0
  82. package/corpus/core/src/agent/durable-background.ts +15 -7
  83. package/corpus/core/src/agent/run-store.ts +13 -4
  84. package/corpus/core/src/audit/actions/export-audit-events.ts +162 -0
  85. package/corpus/core/src/audit/store.ts +8 -3
  86. package/corpus/core/src/audit/types.ts +3 -0
  87. package/corpus/core/src/cli/doctor.ts +396 -0
  88. package/corpus/core/src/cli/index.ts +56 -0
  89. package/corpus/core/src/cli/info.ts +3 -3
  90. package/corpus/core/src/cli/package-lifecycle.ts +821 -0
  91. package/corpus/core/src/client/agent-chat-adapter.ts +9 -9
  92. package/corpus/core/src/guards/db-tool-scoping.ts +129 -0
  93. package/corpus/core/src/guards/index.ts +23 -0
  94. package/corpus/core/src/guards/no-drizzle-push.ts +116 -0
  95. package/corpus/core/src/guards/no-env-credentials.ts +229 -0
  96. package/corpus/core/src/guards/no-env-mutation.ts +114 -0
  97. package/corpus/core/src/guards/no-localhost-fallback.ts +124 -0
  98. package/corpus/core/src/guards/no-unscoped-credentials.ts +227 -0
  99. package/corpus/core/src/guards/no-unscoped-queries.ts +834 -0
  100. package/corpus/core/src/guards/scan-utils.ts +105 -0
  101. package/corpus/core/src/guards/types.ts +30 -0
  102. package/corpus/core/src/org/accept-pending.ts +9 -1
  103. package/corpus/core/src/org/migrations.ts +41 -0
  104. package/corpus/core/src/package-lifecycle/index.ts +1 -0
  105. package/corpus/core/src/package-lifecycle/manifest.ts +120 -0
  106. package/corpus/core/src/provider-api/custom-registry.ts +56 -0
  107. package/corpus/core/src/provider-api/index.ts +2 -0
  108. package/corpus/core/src/secrets/storage.ts +47 -27
  109. package/corpus/core/src/server/action-discovery.ts +4 -0
  110. package/corpus/core/src/server/agent-chat-plugin.ts +54 -12
  111. package/corpus/core/src/server/core-routes-plugin.ts +30 -14
  112. package/corpus/core/src/server/csrf.ts +24 -5
  113. package/corpus/core/src/server/framework-request-handler.ts +34 -0
  114. package/corpus/core/src/server/google-realtime-session.ts +1 -36
  115. package/corpus/core/src/server/poll.ts +24 -2
  116. package/corpus/core/src/server/realtime-voice.ts +9 -0
  117. package/corpus/core/src/server/request-origin.ts +63 -0
  118. package/corpus/core/src/server/transcribe-voice.ts +1 -50
  119. package/corpus/core/src/templates/default/package.json +2 -2
  120. package/corpus/core/src/templates/headless/package.json +0 -1
  121. package/corpus/core/src/templates/workspace-core/.agents/skills/audit-log/SKILL.md +3 -0
  122. package/corpus/core/src/templates/workspace-core/.agents/skills/automations/SKILL.md +1 -1
  123. package/corpus/core/src/templates/workspace-core/package.json +0 -1
  124. package/corpus/core/src/templates/workspace-root/package.json +0 -1
  125. package/corpus/core/src/triggers/actions.ts +11 -3
  126. package/corpus/core/src/triggers/types.ts +7 -1
  127. package/corpus/templates/analytics/actions/compose-dashboard.ts +65 -37
  128. package/corpus/templates/analytics/actions/mutate-dashboard.ts +73 -24
  129. package/corpus/templates/analytics/actions/reorder-dashboard-panels.ts +16 -13
  130. package/corpus/templates/analytics/actions/update-dashboard.ts +53 -42
  131. package/corpus/templates/analytics/changelog/2026-07-10-dashboard-edits-from-the-agent-and-the-ui-at-the-same-time-n.md +6 -0
  132. package/corpus/templates/analytics/changelog/2026-07-10-timed-out-bigquery-jobs-are-now-cancelled-so-they-stop-consu.md +6 -0
  133. package/corpus/templates/analytics/package.json +1 -2
  134. package/corpus/templates/analytics/server/lib/bigquery.ts +3 -2
  135. package/corpus/templates/analytics/server/lib/dashboards-store.ts +154 -11
  136. package/corpus/templates/assets/package.json +1 -2
  137. package/corpus/templates/brain/package.json +1 -2
  138. package/corpus/templates/calendar/package.json +1 -2
  139. package/corpus/templates/chat/package.json +1 -2
  140. package/corpus/templates/clips/changelog/2026-07-10-atomic-view-tracking.md +6 -0
  141. package/corpus/templates/clips/chrome-extension/package.json +3 -2
  142. package/corpus/templates/clips/desktop/package.json +6 -4
  143. package/corpus/templates/clips/desktop/src/lib/recorder.ts +26 -13
  144. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +4 -1
  145. package/corpus/templates/clips/package.json +1 -2
  146. package/corpus/templates/clips/server/db/schema.ts +29 -17
  147. package/corpus/templates/clips/server/plugins/db.ts +8 -0
  148. package/corpus/templates/clips/server/routes/api/view-event.post.ts +260 -105
  149. package/corpus/templates/content/.agents/skills/notion-integration/SKILL.md +16 -0
  150. package/corpus/templates/content/AGENTS.md +72 -62
  151. package/corpus/templates/content/actions/_content-database-source-adapters.ts +84 -0
  152. package/corpus/templates/content/actions/_database-source-utils.ts +206 -96
  153. package/corpus/templates/content/actions/_notion-database-source-adapter.ts +216 -0
  154. package/corpus/templates/content/actions/_position-utils.ts +76 -0
  155. package/corpus/templates/content/actions/_property-utils.ts +31 -20
  156. package/corpus/templates/content/actions/add-database-item.ts +52 -39
  157. package/corpus/templates/content/actions/attach-content-database-source.ts +95 -11
  158. package/corpus/templates/content/actions/change-content-database-source-role.ts +24 -1
  159. package/corpus/templates/content/actions/configure-document-property.ts +36 -27
  160. package/corpus/templates/content/actions/create-content-database.ts +40 -30
  161. package/corpus/templates/content/actions/create-document.ts +39 -32
  162. package/corpus/templates/content/actions/duplicate-document-property.ts +37 -28
  163. package/corpus/templates/content/actions/list-notion-database-sources.ts +80 -0
  164. package/corpus/templates/content/actions/move-document.ts +64 -48
  165. package/corpus/templates/content/actions/refresh-content-database-source.ts +42 -3
  166. package/corpus/templates/content/actions/share-local-file-document.ts +36 -27
  167. package/corpus/templates/content/actions/suggest-source-join-key.ts +39 -16
  168. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +89 -23
  169. package/corpus/templates/content/app/components/editor/database/settings.tsx +1 -1
  170. package/corpus/templates/content/app/hooks/use-content-database.ts +18 -1
  171. package/corpus/templates/content/app/i18n/zh-TW.ts +4 -0
  172. package/corpus/templates/content/app/i18n-data.ts +40 -0
  173. package/corpus/templates/content/changelog/2026-07-10-failed-row-creation-and-source-attach-now-show-an-error-inst.md +6 -0
  174. package/corpus/templates/content/changelog/2026-07-10-items-added-at-the-same-time-no-longer-end-up-with-identical.md +6 -0
  175. package/corpus/templates/content/changelog/2026-07-10-notion-databases-can-add-read-only-details-to-content-tables.md +6 -0
  176. package/corpus/templates/content/package.json +1 -2
  177. package/corpus/templates/content/parity/matrix.md +25 -25
  178. package/corpus/templates/content/parity/matrix.ts +1 -0
  179. package/corpus/templates/content/server/routes/api/documents/[id]/move.patch.ts +43 -26
  180. package/corpus/templates/content/shared/api.ts +16 -1
  181. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +8 -4
  182. package/corpus/templates/design/AGENTS.md +12 -2
  183. package/corpus/templates/design/DEVELOPING.md +10 -6
  184. package/corpus/templates/design/actions/apply-visual-edit.ts +191 -6
  185. package/corpus/templates/design/actions/duplicate-design.ts +31 -25
  186. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +5 -1
  187. package/corpus/templates/design/changelog/2026-07-10-duplicating-a-design-can-no-longer-produce-a-copy-with-missi.md +6 -0
  188. package/corpus/templates/design/changelog/2026-07-10-local-visual-edits-can-now-save-safe-leaf-text-class-and.md +6 -0
  189. package/corpus/templates/design/package.json +1 -1
  190. package/corpus/templates/design/shared/local-jsx-visual-edit.ts +388 -0
  191. package/corpus/templates/dispatch/package.json +1 -2
  192. package/corpus/templates/forms/actions/patch-form-fields.ts +82 -44
  193. package/corpus/templates/forms/changelog/2026-07-10-simultaneous-edits-to-different-form-fields-no-longer-overwr.md +6 -0
  194. package/corpus/templates/forms/package.json +1 -2
  195. package/corpus/templates/macros/package.json +1 -2
  196. package/corpus/templates/mail/app/components/email/EmailThread.tsx +3 -1
  197. package/corpus/templates/mail/changelog/2026-07-10-fixed-calendar-rsvp-buttons-in-emails-firing-duplicate-respo.md +6 -0
  198. package/corpus/templates/mail/package.json +1 -2
  199. package/corpus/templates/plan/actions/restore-plan-version.ts +126 -96
  200. package/corpus/templates/plan/changelog/2026-07-10-restoring-a-plan-version-can-no-longer-leave-a-plan-half-res.md +6 -0
  201. package/corpus/templates/plan/package.json +1 -2
  202. package/corpus/templates/plan/server/plans.ts +9 -2
  203. package/corpus/templates/slides/actions/list-decks.ts +28 -0
  204. package/corpus/templates/slides/app/components/deck/MermaidRenderer.tsx +41 -26
  205. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +4 -8
  206. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +2 -4
  207. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -8
  208. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +5 -12
  209. package/corpus/templates/slides/app/components/editor/HistoryPanel.tsx +3 -6
  210. package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +3 -6
  211. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +2 -5
  212. package/corpus/templates/slides/app/context/DeckContext.tsx +47 -4
  213. package/corpus/templates/slides/app/pages/DeckEditor.tsx +11 -30
  214. package/corpus/templates/slides/app/pages/Index.tsx +2 -3
  215. package/corpus/templates/slides/app/root.tsx +0 -5
  216. package/corpus/templates/slides/changelog/2026-07-10-deck-lists-load-much-faster-and-no-longer-re-download-slide-.md +6 -0
  217. package/corpus/templates/slides/package.json +1 -2
  218. package/dist/a2a/auth-policy.d.ts +19 -0
  219. package/dist/a2a/auth-policy.d.ts.map +1 -1
  220. package/dist/a2a/auth-policy.js +32 -0
  221. package/dist/a2a/auth-policy.js.map +1 -1
  222. package/dist/agent/durable-background.d.ts +8 -5
  223. package/dist/agent/durable-background.d.ts.map +1 -1
  224. package/dist/agent/durable-background.js +15 -8
  225. package/dist/agent/durable-background.js.map +1 -1
  226. package/dist/agent/run-store.d.ts.map +1 -1
  227. package/dist/agent/run-store.js +9 -4
  228. package/dist/agent/run-store.js.map +1 -1
  229. package/dist/audit/actions/export-audit-events.d.ts +27 -0
  230. package/dist/audit/actions/export-audit-events.d.ts.map +1 -0
  231. package/dist/audit/actions/export-audit-events.js +144 -0
  232. package/dist/audit/actions/export-audit-events.js.map +1 -0
  233. package/dist/audit/store.d.ts +1 -0
  234. package/dist/audit/store.d.ts.map +1 -1
  235. package/dist/audit/store.js +8 -3
  236. package/dist/audit/store.js.map +1 -1
  237. package/dist/audit/types.d.ts +3 -0
  238. package/dist/audit/types.d.ts.map +1 -1
  239. package/dist/audit/types.js.map +1 -1
  240. package/dist/cli/doctor.d.ts +83 -0
  241. package/dist/cli/doctor.d.ts.map +1 -0
  242. package/dist/cli/doctor.js +277 -0
  243. package/dist/cli/doctor.js.map +1 -0
  244. package/dist/cli/index.js +50 -0
  245. package/dist/cli/index.js.map +1 -1
  246. package/dist/cli/info.js +3 -3
  247. package/dist/cli/info.js.map +1 -1
  248. package/dist/cli/package-lifecycle.d.ts +50 -0
  249. package/dist/cli/package-lifecycle.d.ts.map +1 -0
  250. package/dist/cli/package-lifecycle.js +560 -0
  251. package/dist/cli/package-lifecycle.js.map +1 -0
  252. package/dist/client/agent-chat-adapter.js +7 -3
  253. package/dist/client/agent-chat-adapter.js.map +1 -1
  254. package/dist/collab/struct-routes.d.ts +1 -1
  255. package/dist/guards/db-tool-scoping.d.ts +38 -0
  256. package/dist/guards/db-tool-scoping.d.ts.map +1 -0
  257. package/dist/guards/db-tool-scoping.js +114 -0
  258. package/dist/guards/db-tool-scoping.js.map +1 -0
  259. package/dist/guards/index.d.ts +23 -0
  260. package/dist/guards/index.d.ts.map +1 -0
  261. package/dist/guards/index.js +19 -0
  262. package/dist/guards/index.js.map +1 -0
  263. package/dist/guards/no-drizzle-push.d.ts +14 -0
  264. package/dist/guards/no-drizzle-push.d.ts.map +1 -0
  265. package/dist/guards/no-drizzle-push.js +97 -0
  266. package/dist/guards/no-drizzle-push.js.map +1 -0
  267. package/dist/guards/no-env-credentials.d.ts +36 -0
  268. package/dist/guards/no-env-credentials.d.ts.map +1 -0
  269. package/dist/guards/no-env-credentials.js +204 -0
  270. package/dist/guards/no-env-credentials.js.map +1 -0
  271. package/dist/guards/no-env-mutation.d.ts +23 -0
  272. package/dist/guards/no-env-mutation.d.ts.map +1 -0
  273. package/dist/guards/no-env-mutation.js +90 -0
  274. package/dist/guards/no-env-mutation.js.map +1 -0
  275. package/dist/guards/no-localhost-fallback.d.ts +30 -0
  276. package/dist/guards/no-localhost-fallback.d.ts.map +1 -0
  277. package/dist/guards/no-localhost-fallback.js +105 -0
  278. package/dist/guards/no-localhost-fallback.js.map +1 -0
  279. package/dist/guards/no-unscoped-credentials.d.ts +28 -0
  280. package/dist/guards/no-unscoped-credentials.d.ts.map +1 -0
  281. package/dist/guards/no-unscoped-credentials.js +199 -0
  282. package/dist/guards/no-unscoped-credentials.js.map +1 -0
  283. package/dist/guards/no-unscoped-queries.d.ts +32 -0
  284. package/dist/guards/no-unscoped-queries.d.ts.map +1 -0
  285. package/dist/guards/no-unscoped-queries.js +720 -0
  286. package/dist/guards/no-unscoped-queries.js.map +1 -0
  287. package/dist/guards/scan-utils.d.ts +28 -0
  288. package/dist/guards/scan-utils.d.ts.map +1 -0
  289. package/dist/guards/scan-utils.js +92 -0
  290. package/dist/guards/scan-utils.js.map +1 -0
  291. package/dist/guards/types.d.ts +28 -0
  292. package/dist/guards/types.d.ts.map +1 -0
  293. package/dist/guards/types.js +11 -0
  294. package/dist/guards/types.js.map +1 -0
  295. package/dist/org/accept-pending.d.ts.map +1 -1
  296. package/dist/org/accept-pending.js +9 -1
  297. package/dist/org/accept-pending.js.map +1 -1
  298. package/dist/org/migrations.d.ts +7 -2
  299. package/dist/org/migrations.d.ts.map +1 -1
  300. package/dist/org/migrations.js +41 -0
  301. package/dist/org/migrations.js.map +1 -1
  302. package/dist/package-lifecycle/index.d.ts +2 -0
  303. package/dist/package-lifecycle/index.d.ts.map +1 -0
  304. package/dist/package-lifecycle/index.js +2 -0
  305. package/dist/package-lifecycle/index.js.map +1 -0
  306. package/dist/package-lifecycle/manifest.d.ts +24 -0
  307. package/dist/package-lifecycle/manifest.d.ts.map +1 -0
  308. package/dist/package-lifecycle/manifest.js +81 -0
  309. package/dist/package-lifecycle/manifest.js.map +1 -0
  310. package/dist/progress/routes.d.ts +1 -1
  311. package/dist/provider-api/custom-registry.d.ts +32 -1
  312. package/dist/provider-api/custom-registry.d.ts.map +1 -1
  313. package/dist/provider-api/custom-registry.js +41 -1
  314. package/dist/provider-api/custom-registry.js.map +1 -1
  315. package/dist/provider-api/index.d.ts +1 -1
  316. package/dist/provider-api/index.d.ts.map +1 -1
  317. package/dist/provider-api/index.js +1 -1
  318. package/dist/provider-api/index.js.map +1 -1
  319. package/dist/secrets/storage.d.ts.map +1 -1
  320. package/dist/secrets/storage.js +45 -27
  321. package/dist/secrets/storage.js.map +1 -1
  322. package/dist/server/action-discovery.d.ts.map +1 -1
  323. package/dist/server/action-discovery.js +4 -0
  324. package/dist/server/action-discovery.js.map +1 -1
  325. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  326. package/dist/server/agent-chat-plugin.js +47 -13
  327. package/dist/server/agent-chat-plugin.js.map +1 -1
  328. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  329. package/dist/server/core-routes-plugin.js +23 -12
  330. package/dist/server/core-routes-plugin.js.map +1 -1
  331. package/dist/server/csrf.d.ts.map +1 -1
  332. package/dist/server/csrf.js +18 -5
  333. package/dist/server/csrf.js.map +1 -1
  334. package/dist/server/framework-request-handler.d.ts.map +1 -1
  335. package/dist/server/framework-request-handler.js +34 -0
  336. package/dist/server/framework-request-handler.js.map +1 -1
  337. package/dist/server/google-realtime-session.d.ts.map +1 -1
  338. package/dist/server/google-realtime-session.js +2 -34
  339. package/dist/server/google-realtime-session.js.map +1 -1
  340. package/dist/server/poll.d.ts.map +1 -1
  341. package/dist/server/poll.js +24 -2
  342. package/dist/server/poll.js.map +1 -1
  343. package/dist/server/realtime-voice.d.ts.map +1 -1
  344. package/dist/server/realtime-voice.js +9 -0
  345. package/dist/server/realtime-voice.js.map +1 -1
  346. package/dist/server/request-origin.d.ts +10 -0
  347. package/dist/server/request-origin.d.ts.map +1 -0
  348. package/dist/server/request-origin.js +60 -0
  349. package/dist/server/request-origin.js.map +1 -0
  350. package/dist/server/transcribe-voice.d.ts.map +1 -1
  351. package/dist/server/transcribe-voice.js +2 -48
  352. package/dist/server/transcribe-voice.js.map +1 -1
  353. package/dist/templates/default/package.json +2 -2
  354. package/dist/templates/headless/package.json +0 -1
  355. package/dist/templates/workspace-core/.agents/skills/audit-log/SKILL.md +3 -0
  356. package/dist/templates/workspace-core/.agents/skills/automations/SKILL.md +1 -1
  357. package/dist/templates/workspace-core/package.json +0 -1
  358. package/dist/templates/workspace-root/package.json +0 -1
  359. package/dist/triggers/actions.d.ts.map +1 -1
  360. package/dist/triggers/actions.js +8 -3
  361. package/dist/triggers/actions.js.map +1 -1
  362. package/dist/triggers/types.d.ts +7 -1
  363. package/dist/triggers/types.d.ts.map +1 -1
  364. package/dist/triggers/types.js.map +1 -1
  365. package/docs/content/audit-log.mdx +2 -1
  366. package/docs/content/automations.mdx +7 -7
  367. package/docs/content/creating-templates.mdx +1 -1
  368. package/docs/content/internationalization.mdx +2 -1
  369. package/docs/content/locales/ar-SA/audit-log.mdx +2 -1
  370. package/docs/content/locales/ar-SA/automations.mdx +7 -7
  371. package/docs/content/locales/ar-SA/getting-started.mdx +7 -1
  372. package/docs/content/locales/ar-SA/internationalization.mdx +1 -1
  373. package/docs/content/locales/ar-SA/package-lifecycle.mdx +19 -0
  374. package/docs/content/locales/ar-SA/sandbox-adapters.mdx +18 -7
  375. package/docs/content/locales/ar-SA/security.mdx +8 -0
  376. package/docs/content/locales/de-DE/audit-log.mdx +2 -1
  377. package/docs/content/locales/de-DE/automations.mdx +7 -7
  378. package/docs/content/locales/de-DE/getting-started.mdx +7 -1
  379. package/docs/content/locales/de-DE/internationalization.mdx +1 -1
  380. package/docs/content/locales/de-DE/package-lifecycle.mdx +22 -0
  381. package/docs/content/locales/de-DE/sandbox-adapters.mdx +18 -7
  382. package/docs/content/locales/de-DE/security.mdx +8 -0
  383. package/docs/content/locales/es-ES/audit-log.mdx +2 -1
  384. package/docs/content/locales/es-ES/automations.mdx +7 -7
  385. package/docs/content/locales/es-ES/getting-started.mdx +7 -1
  386. package/docs/content/locales/es-ES/internationalization.mdx +1 -1
  387. package/docs/content/locales/es-ES/package-lifecycle.mdx +22 -0
  388. package/docs/content/locales/es-ES/sandbox-adapters.mdx +18 -7
  389. package/docs/content/locales/es-ES/security.mdx +8 -0
  390. package/docs/content/locales/fr-FR/audit-log.mdx +2 -1
  391. package/docs/content/locales/fr-FR/automations.mdx +7 -7
  392. package/docs/content/locales/fr-FR/getting-started.mdx +7 -1
  393. package/docs/content/locales/fr-FR/internationalization.mdx +1 -1
  394. package/docs/content/locales/fr-FR/package-lifecycle.mdx +21 -0
  395. package/docs/content/locales/fr-FR/sandbox-adapters.mdx +19 -8
  396. package/docs/content/locales/fr-FR/security.mdx +8 -0
  397. package/docs/content/locales/hi-IN/audit-log.mdx +2 -1
  398. package/docs/content/locales/hi-IN/automations.mdx +7 -7
  399. package/docs/content/locales/hi-IN/getting-started.mdx +7 -1
  400. package/docs/content/locales/hi-IN/internationalization.mdx +1 -1
  401. package/docs/content/locales/hi-IN/package-lifecycle.mdx +20 -0
  402. package/docs/content/locales/hi-IN/sandbox-adapters.mdx +18 -7
  403. package/docs/content/locales/hi-IN/security.mdx +8 -0
  404. package/docs/content/locales/ja-JP/audit-log.mdx +2 -1
  405. package/docs/content/locales/ja-JP/automations.mdx +7 -7
  406. package/docs/content/locales/ja-JP/getting-started.mdx +7 -1
  407. package/docs/content/locales/ja-JP/internationalization.mdx +1 -1
  408. package/docs/content/locales/ja-JP/package-lifecycle.mdx +21 -0
  409. package/docs/content/locales/ja-JP/sandbox-adapters.mdx +17 -6
  410. package/docs/content/locales/ja-JP/security.mdx +8 -0
  411. package/docs/content/locales/ko-KR/audit-log.mdx +2 -1
  412. package/docs/content/locales/ko-KR/automations.mdx +7 -7
  413. package/docs/content/locales/ko-KR/getting-started.mdx +7 -1
  414. package/docs/content/locales/ko-KR/internationalization.mdx +1 -1
  415. package/docs/content/locales/ko-KR/package-lifecycle.mdx +20 -0
  416. package/docs/content/locales/ko-KR/sandbox-adapters.mdx +17 -6
  417. package/docs/content/locales/ko-KR/security.mdx +8 -0
  418. package/docs/content/locales/pt-BR/audit-log.mdx +2 -1
  419. package/docs/content/locales/pt-BR/automations.mdx +7 -7
  420. package/docs/content/locales/pt-BR/getting-started.mdx +7 -1
  421. package/docs/content/locales/pt-BR/internationalization.mdx +1 -1
  422. package/docs/content/locales/pt-BR/package-lifecycle.mdx +21 -0
  423. package/docs/content/locales/pt-BR/sandbox-adapters.mdx +18 -7
  424. package/docs/content/locales/pt-BR/security.mdx +8 -0
  425. package/docs/content/locales/zh-CN/audit-log.mdx +2 -1
  426. package/docs/content/locales/zh-CN/automations.mdx +7 -7
  427. package/docs/content/locales/zh-CN/getting-started.mdx +6 -4
  428. package/docs/content/locales/zh-CN/internationalization.mdx +1 -1
  429. package/docs/content/locales/zh-CN/package-lifecycle.mdx +19 -0
  430. package/docs/content/locales/zh-CN/sandbox-adapters.mdx +17 -6
  431. package/docs/content/locales/zh-CN/security.mdx +8 -0
  432. package/docs/content/locales/zh-TW/audit-log.mdx +2 -1
  433. package/docs/content/locales/zh-TW/automations.mdx +7 -7
  434. package/docs/content/locales/zh-TW/getting-started.mdx +6 -4
  435. package/docs/content/locales/zh-TW/internationalization.mdx +1 -1
  436. package/docs/content/locales/zh-TW/package-lifecycle.mdx +19 -0
  437. package/docs/content/locales/zh-TW/sandbox-adapters.mdx +18 -7
  438. package/docs/content/locales/zh-TW/security.mdx +8 -0
  439. package/docs/content/package-lifecycle.mdx +51 -0
  440. package/docs/content/sandbox-adapters.mdx +16 -5
  441. package/package.json +10 -3
  442. package/src/templates/default/package.json +2 -2
  443. package/src/templates/headless/package.json +0 -1
  444. package/src/templates/workspace-core/.agents/skills/audit-log/SKILL.md +3 -0
  445. package/src/templates/workspace-core/.agents/skills/automations/SKILL.md +1 -1
  446. package/src/templates/workspace-core/package.json +0 -1
  447. package/src/templates/workspace-root/package.json +0 -1
  448. package/corpus/templates/slides/app/components/ui/toast.tsx +0 -1
  449. package/corpus/templates/slides/app/components/ui/toaster.tsx +0 -1
  450. package/corpus/templates/slides/app/hooks/use-toast.ts +0 -1
@@ -76,6 +76,12 @@ import {
76
76
  type ExistingBuilderSourceRowIdentity,
77
77
  } from "./_builder-cms-source-adapter.js";
78
78
  import { mergeBuilderCmsWriteSettingsIntoJson } from "./_builder-cms-write-settings.js";
79
+ import {
80
+ databaseItemsPositionScope,
81
+ documentsPositionScope,
82
+ propertyDefinitionsPositionScope,
83
+ withPositionLock,
84
+ } from "./_position-utils.js";
79
85
  import { listPropertiesForDatabase, nanoid } from "./_property-utils.js";
80
86
  import { isEffectivelyEmptyDocumentContent } from "./update-document.js";
81
87
 
@@ -189,7 +195,12 @@ function normalizeWriteOwner(
189
195
  function normalizeSourceType(
190
196
  value: string | null | undefined,
191
197
  ): ContentDatabaseSourceType {
192
- if (value === "builder-cms" || value === "local-table") return value;
198
+ if (
199
+ value === "builder-cms" ||
200
+ value === "local-table" ||
201
+ value === "notion-database"
202
+ )
203
+ return value;
193
204
  return "mock-local";
194
205
  }
195
206
 
@@ -279,6 +290,7 @@ function sourceMetadataLabel(
279
290
  ) {
280
291
  if (sourceType === "builder-cms") return `builder.cms.${sourceTable}`;
281
292
  if (sourceType === "local-table") return `local.table.${sourceTable}`;
293
+ if (sourceType === "notion-database") return `notion.database.${sourceTable}`;
282
294
  return `mock-local.${sourceTable}`;
283
295
  }
284
296
 
@@ -3102,6 +3114,20 @@ export function serializeSourceMetadataRecord(args: {
3102
3114
  args.builderModelFields ?? existingMetadata?.builderModelFields,
3103
3115
  });
3104
3116
  }
3117
+ if (args.sourceType === "notion-database") {
3118
+ return JSON.stringify({
3119
+ primaryKey: "id",
3120
+ titleField: "title",
3121
+ naturalKeyField: null,
3122
+ pushMode: "none",
3123
+ pushModeLabel: "Read only",
3124
+ pushModeDescription:
3125
+ "Notion database sources are read-only in this pilot.",
3126
+ notes:
3127
+ "Notion values are refreshed through the current user's OAuth connection; write-back is disabled.",
3128
+ label: sourceMetadataLabel(args.sourceType, args.sourceTable),
3129
+ });
3130
+ }
3105
3131
  return JSON.stringify({
3106
3132
  primaryKey: "id",
3107
3133
  titleField: "title",
@@ -3180,6 +3206,21 @@ export function sourceCapabilitiesForType(
3180
3206
  readOnlyRefresh: true,
3181
3207
  });
3182
3208
  }
3209
+ if (sourceType === "notion-database") {
3210
+ return serializeSourceCapabilitiesRecord({
3211
+ canRefresh: true,
3212
+ canCreateChangeSets: false,
3213
+ canWriteFields: false,
3214
+ canWriteBody: false,
3215
+ canPush: false,
3216
+ canPull: true,
3217
+ canPublish: false,
3218
+ canDelete: false,
3219
+ canStageLocalRevision: false,
3220
+ liveWritesEnabled: false,
3221
+ readOnlyRefresh: true,
3222
+ });
3223
+ }
3183
3224
  return serializeSourceCapabilitiesRecord();
3184
3225
  }
3185
3226
 
@@ -4168,82 +4209,106 @@ export async function importBuilderCmsEntriesAsDatabaseItems(args: {
4168
4209
  const existingTitles = new Set(
4169
4210
  currentItems.map((row) => row.document.title.trim().toLowerCase()),
4170
4211
  );
4171
- const [maxDocPos] = await db
4172
- .select({ max: sql<number>`COALESCE(MAX(position), -1)` })
4173
- .from(schema.documents)
4174
- .where(
4175
- and(
4176
- eq(schema.documents.ownerEmail, args.database.ownerEmail),
4177
- eq(schema.documents.parentId, args.database.documentId),
4178
- ),
4179
- );
4180
- const [maxItemPos] = await db
4181
- .select({ max: sql<number>`COALESCE(MAX(position), -1)` })
4182
- .from(schema.contentDatabaseItems)
4183
- .where(eq(schema.contentDatabaseItems.databaseId, args.database.id));
4184
-
4185
- let nextDocPosition = (maxDocPos?.max ?? -1) + 1;
4186
- let nextItemPosition = (maxItemPos?.max ?? -1) + 1;
4187
- const documentRows: (typeof schema.documents.$inferInsert)[] = [];
4188
- const itemRows: (typeof schema.contentDatabaseItems.$inferInsert)[] = [];
4189
-
4190
- for (const entry of args.entries) {
4191
- if (
4192
- builderCmsEntryAlreadyRepresented({
4193
- entry,
4194
- sourceTable: args.sourceTable,
4195
- existingSourceRows: args.existingSourceRows ?? [],
4196
- })
4197
- ) {
4198
- continue;
4199
- }
4200
4212
 
4201
- const title = entry.title.trim() || entry.id;
4202
- const titleKey = title.toLowerCase();
4203
- if (!args.skipTitleDedup && existingTitles.has(titleKey)) continue;
4204
- existingTitles.add(titleKey);
4205
-
4206
- const documentId = nanoid();
4207
- const itemId = nanoid();
4208
- documentRows.push({
4209
- id: documentId,
4210
- ownerEmail: args.database.ownerEmail,
4211
- orgId: args.database.orgId,
4212
- parentId: args.database.documentId,
4213
- title,
4214
- content: "",
4215
- icon: null,
4216
- position: nextDocPosition++,
4217
- isFavorite: 0,
4218
- hideFromSearch: databaseDocument?.hideFromSearch ?? 0,
4219
- visibility: databaseDocument?.visibility ?? "private",
4220
- createdAt: args.now,
4221
- updatedAt: args.now,
4222
- });
4223
- itemRows.push({
4224
- id: itemId,
4225
- ownerEmail: args.database.ownerEmail,
4226
- orgId: args.database.orgId,
4227
- databaseId: args.database.id,
4228
- documentId,
4229
- position: nextItemPosition++,
4230
- bodyHydrationStatus: "pending",
4231
- bodyHydrationError: null,
4232
- createdAt: args.now,
4233
- updatedAt: args.now,
4234
- });
4235
- importedEntriesByDocumentId.set(documentId, entry);
4236
- }
4237
- await db.transaction(async (tx) => {
4238
- for (const chunk of chunks(documentRows, bulkChunkSizeForColumnCount(13))) {
4239
- await tx.insert(schema.documents).values(chunk);
4240
- }
4241
- for (const chunk of chunks(itemRows, bulkChunkSizeForColumnCount(10))) {
4242
- await tx.insert(schema.contentDatabaseItems).values(chunk);
4243
- }
4244
- });
4213
+ // Reads MAX(position) for both `documents` and `content_database_items`
4214
+ // then batch-inserts at MAX+1.. — serialize the whole read-through-write
4215
+ // span per scope so a concurrent import/add/move targeting the same parent
4216
+ // document or the same database can't read the same MAX (see
4217
+ // _position-utils.ts).
4218
+ return withPositionLock(
4219
+ documentsPositionScope(args.database.ownerEmail, args.database.documentId),
4220
+ () =>
4221
+ withPositionLock(
4222
+ databaseItemsPositionScope(args.database.id),
4223
+ async () => {
4224
+ const [maxDocPos] = await db
4225
+ .select({ max: sql<number>`COALESCE(MAX(position), -1)` })
4226
+ .from(schema.documents)
4227
+ .where(
4228
+ and(
4229
+ eq(schema.documents.ownerEmail, args.database.ownerEmail),
4230
+ eq(schema.documents.parentId, args.database.documentId),
4231
+ ),
4232
+ );
4233
+ const [maxItemPos] = await db
4234
+ .select({ max: sql<number>`COALESCE(MAX(position), -1)` })
4235
+ .from(schema.contentDatabaseItems)
4236
+ .where(
4237
+ eq(schema.contentDatabaseItems.databaseId, args.database.id),
4238
+ );
4245
4239
 
4246
- return { imported: itemRows.length, importedEntriesByDocumentId };
4240
+ let nextDocPosition = (maxDocPos?.max ?? -1) + 1;
4241
+ let nextItemPosition = (maxItemPos?.max ?? -1) + 1;
4242
+ const documentRows: (typeof schema.documents.$inferInsert)[] = [];
4243
+ const itemRows: (typeof schema.contentDatabaseItems.$inferInsert)[] =
4244
+ [];
4245
+
4246
+ for (const entry of args.entries) {
4247
+ if (
4248
+ builderCmsEntryAlreadyRepresented({
4249
+ entry,
4250
+ sourceTable: args.sourceTable,
4251
+ existingSourceRows: args.existingSourceRows ?? [],
4252
+ })
4253
+ ) {
4254
+ continue;
4255
+ }
4256
+
4257
+ const title = entry.title.trim() || entry.id;
4258
+ const titleKey = title.toLowerCase();
4259
+ if (!args.skipTitleDedup && existingTitles.has(titleKey)) continue;
4260
+ existingTitles.add(titleKey);
4261
+
4262
+ const documentId = nanoid();
4263
+ const itemId = nanoid();
4264
+ documentRows.push({
4265
+ id: documentId,
4266
+ ownerEmail: args.database.ownerEmail,
4267
+ orgId: args.database.orgId,
4268
+ parentId: args.database.documentId,
4269
+ title,
4270
+ content: "",
4271
+ icon: null,
4272
+ position: nextDocPosition++,
4273
+ isFavorite: 0,
4274
+ hideFromSearch: databaseDocument?.hideFromSearch ?? 0,
4275
+ visibility: databaseDocument?.visibility ?? "private",
4276
+ createdAt: args.now,
4277
+ updatedAt: args.now,
4278
+ });
4279
+ itemRows.push({
4280
+ id: itemId,
4281
+ ownerEmail: args.database.ownerEmail,
4282
+ orgId: args.database.orgId,
4283
+ databaseId: args.database.id,
4284
+ documentId,
4285
+ position: nextItemPosition++,
4286
+ bodyHydrationStatus: "pending",
4287
+ bodyHydrationError: null,
4288
+ createdAt: args.now,
4289
+ updatedAt: args.now,
4290
+ });
4291
+ importedEntriesByDocumentId.set(documentId, entry);
4292
+ }
4293
+ await db.transaction(async (tx) => {
4294
+ for (const chunk of chunks(
4295
+ documentRows,
4296
+ bulkChunkSizeForColumnCount(13),
4297
+ )) {
4298
+ await tx.insert(schema.documents).values(chunk);
4299
+ }
4300
+ for (const chunk of chunks(
4301
+ itemRows,
4302
+ bulkChunkSizeForColumnCount(10),
4303
+ )) {
4304
+ await tx.insert(schema.contentDatabaseItems).values(chunk);
4305
+ }
4306
+ });
4307
+
4308
+ return { imported: itemRows.length, importedEntriesByDocumentId };
4309
+ },
4310
+ ),
4311
+ );
4247
4312
  }
4248
4313
 
4249
4314
  export async function resyncBuilderCmsSourceSnapshot(args: {
@@ -4825,7 +4890,10 @@ export async function storeSecondarySourceRows(args: {
4825
4890
  sourceDisplayKey:
4826
4891
  entry.title?.trim() || `${args.sourceTable}-${index + 1}`,
4827
4892
  sourceValuesJson: JSON.stringify(entry.sourceValues ?? {}),
4828
- provenance: "secondary source row",
4893
+ provenance:
4894
+ args.sourceType === "notion-database"
4895
+ ? "Notion database read adapter"
4896
+ : "secondary source row",
4829
4897
  syncState: "linked" as const,
4830
4898
  freshness: "fresh" as const,
4831
4899
  lastSyncedAt: args.now,
@@ -4844,6 +4912,7 @@ export async function storeSecondarySourceRows(args: {
4844
4912
  export async function seedSecondarySourceFields(args: {
4845
4913
  sourceId: string;
4846
4914
  ownerEmail: string;
4915
+ sourceType?: ContentDatabaseSourceType;
4847
4916
  modelFields: BuilderCmsModelFieldSummary[];
4848
4917
  sampleEntry?: BuilderCmsSourceEntry;
4849
4918
  now: string;
@@ -4909,7 +4978,10 @@ export async function seedSecondarySourceFields(args: {
4909
4978
  mappingType: "property" as const,
4910
4979
  writeOwner: "source" as const,
4911
4980
  readOnly: 1,
4912
- provenance: "secondary source field",
4981
+ provenance:
4982
+ args.sourceType === "notion-database"
4983
+ ? "Notion database read adapter"
4984
+ : "secondary source field",
4913
4985
  freshness: "fresh" as const,
4914
4986
  lastSyncedAt: args.now,
4915
4987
  createdAt: existing?.createdAt ?? args.now,
@@ -5008,6 +5080,39 @@ export async function updateBuilderCmsSourceReadMetadata(args: {
5008
5080
  .where(eq(schema.contentDatabaseSources.id, args.sourceId));
5009
5081
  }
5010
5082
 
5083
+ export async function updateReadOnlySourceMetadata(args: {
5084
+ sourceId: string;
5085
+ sourceType: ContentDatabaseSourceType;
5086
+ sourceTable: string;
5087
+ fetchedAt: string;
5088
+ now: string;
5089
+ message: string | null;
5090
+ metadata?: Record<string, unknown>;
5091
+ syncState?: ContentDatabaseSourceSyncState;
5092
+ }) {
5093
+ const db = getDb();
5094
+ const [current] = await db
5095
+ .select({ metadataJson: schema.contentDatabaseSources.metadataJson })
5096
+ .from(schema.contentDatabaseSources)
5097
+ .where(eq(schema.contentDatabaseSources.id, args.sourceId))
5098
+ .limit(1);
5099
+ const existing =
5100
+ parseObject<Record<string, unknown>>(current?.metadataJson) ?? {};
5101
+ await db
5102
+ .update(schema.contentDatabaseSources)
5103
+ .set({
5104
+ syncState: args.syncState ?? (args.message ? "refreshing" : "linked"),
5105
+ freshness: args.message ? "stale" : "fresh",
5106
+ capabilitiesJson: sourceCapabilitiesForType(args.sourceType),
5107
+ metadataJson: JSON.stringify({ ...existing, ...(args.metadata ?? {}) }),
5108
+ lastRefreshedAt: args.now,
5109
+ lastSourceUpdatedAt: args.fetchedAt,
5110
+ lastError: null,
5111
+ updatedAt: args.now,
5112
+ })
5113
+ .where(eq(schema.contentDatabaseSources.id, args.sourceId));
5114
+ }
5115
+
5011
5116
  export async function getExistingSource(databaseId: string) {
5012
5117
  const db = getDb();
5013
5118
  const [source] = await db
@@ -5154,26 +5259,31 @@ export async function ensureDatabaseSourceProperty(args: {
5154
5259
  .set({ optionsJson, updatedAt: args.now })
5155
5260
  .where(eq(schema.documentPropertyDefinitions.id, existing.id));
5156
5261
  } else {
5157
- const [maxPos] = await db
5158
- .select({ max: sql<number>`COALESCE(MAX(position), -1)` })
5159
- .from(schema.documentPropertyDefinitions)
5160
- .where(
5161
- eq(schema.documentPropertyDefinitions.databaseId, args.database.id),
5162
- );
5163
5262
  propertyId = crypto.randomUUID();
5164
- await db.insert(schema.documentPropertyDefinitions).values({
5165
- id: propertyId,
5166
- ownerEmail: args.database.ownerEmail,
5167
- orgId: args.database.orgId,
5168
- databaseId: args.database.id,
5169
- name: SOURCE_PROPERTY_NAME,
5170
- type: "select",
5171
- visibility: "always_show",
5172
- optionsJson,
5173
- position: (maxPos?.max ?? -1) + 1,
5174
- createdAt: args.now,
5175
- updatedAt: args.now,
5176
- });
5263
+ await withPositionLock(
5264
+ propertyDefinitionsPositionScope(args.database.id),
5265
+ async () => {
5266
+ const [maxPos] = await db
5267
+ .select({ max: sql<number>`COALESCE(MAX(position), -1)` })
5268
+ .from(schema.documentPropertyDefinitions)
5269
+ .where(
5270
+ eq(schema.documentPropertyDefinitions.databaseId, args.database.id),
5271
+ );
5272
+ await db.insert(schema.documentPropertyDefinitions).values({
5273
+ id: propertyId,
5274
+ ownerEmail: args.database.ownerEmail,
5275
+ orgId: args.database.orgId,
5276
+ databaseId: args.database.id,
5277
+ name: SOURCE_PROPERTY_NAME,
5278
+ type: "select",
5279
+ visibility: "always_show",
5280
+ optionsJson,
5281
+ position: (maxPos?.max ?? -1) + 1,
5282
+ createdAt: args.now,
5283
+ updatedAt: args.now,
5284
+ });
5285
+ },
5286
+ );
5177
5287
  }
5178
5288
 
5179
5289
  // A row's Source value IS its owning source id (= the option id); unsourced
@@ -0,0 +1,216 @@
1
+ import { getRequestUserEmail } from "@agent-native/core/server";
2
+
3
+ import {
4
+ getNotionConnectionForOwner,
5
+ notionFetch,
6
+ } from "../server/lib/notion.js";
7
+ import type {
8
+ BuilderCmsModelFieldSummary,
9
+ DocumentPropertyValue,
10
+ } from "../shared/api.js";
11
+ import type { BuilderCmsSourceEntry } from "./_builder-cms-source-adapter.js";
12
+ import type { ContentDatabaseSourceReadResult } from "./_content-database-source-adapters.js";
13
+
14
+ const NOTION_SOURCE_PAGE_SIZE = 100;
15
+ const NOTION_SOURCE_MAX_PAGES = 5;
16
+
17
+ type NotionProperty = {
18
+ id?: string;
19
+ name?: string;
20
+ type?: string;
21
+ [key: string]: unknown;
22
+ };
23
+
24
+ type NotionPage = {
25
+ object?: string;
26
+ id: string;
27
+ url?: string;
28
+ last_edited_time?: string;
29
+ properties?: Record<string, NotionProperty>;
30
+ };
31
+
32
+ function plainText(value: unknown): string {
33
+ return Array.isArray(value)
34
+ ? value
35
+ .map((part) =>
36
+ typeof part === "object" && part && "plain_text" in part
37
+ ? String((part as { plain_text?: unknown }).plain_text ?? "")
38
+ : "",
39
+ )
40
+ .join("")
41
+ : "";
42
+ }
43
+
44
+ const supportedTypes = new Set([
45
+ "title",
46
+ "rich_text",
47
+ "number",
48
+ "checkbox",
49
+ "url",
50
+ "email",
51
+ "phone_number",
52
+ "date",
53
+ "select",
54
+ "status",
55
+ "multi_select",
56
+ ]);
57
+
58
+ function sourceFieldType(type: string) {
59
+ if (!supportedTypes.has(type)) return `unsupported:${type || "unknown"}`;
60
+ if (type === "rich_text" || type === "title") return "text";
61
+ if (type === "phone_number") return "phone";
62
+ return type;
63
+ }
64
+
65
+ function propertyValue(property: NotionProperty): DocumentPropertyValue {
66
+ const type = property.type ?? "unknown";
67
+ if (!supportedTypes.has(type)) {
68
+ return `[Unsupported Notion property: ${type}]`;
69
+ }
70
+ if (type === "title" || type === "rich_text")
71
+ return plainText(property[type]);
72
+ if (type === "number")
73
+ return typeof property.number === "number" ? property.number : null;
74
+ if (type === "checkbox") return property.checkbox === true;
75
+ if (type === "url" || type === "email" || type === "phone_number") {
76
+ const value = property[type];
77
+ return typeof value === "string" ? value : null;
78
+ }
79
+ if (type === "date") {
80
+ const date = property.date as
81
+ | { start?: unknown; end?: unknown }
82
+ | null
83
+ | undefined;
84
+ return typeof date?.start === "string"
85
+ ? {
86
+ start: date.start,
87
+ end: typeof date.end === "string" ? date.end : null,
88
+ includeTime: date.start.includes("T"),
89
+ }
90
+ : null;
91
+ }
92
+ if (type === "select" || type === "status") {
93
+ const option = property[type] as { name?: unknown } | null | undefined;
94
+ return typeof option?.name === "string" ? option.name : null;
95
+ }
96
+ if (type === "multi_select") {
97
+ return Array.isArray(property.multi_select)
98
+ ? property.multi_select.flatMap((option) =>
99
+ typeof option === "object" &&
100
+ option &&
101
+ "name" in option &&
102
+ typeof (option as { name?: unknown }).name === "string"
103
+ ? [(option as { name: string }).name]
104
+ : [],
105
+ )
106
+ : [];
107
+ }
108
+ return null;
109
+ }
110
+
111
+ function titleFromProperties(properties: Record<string, NotionProperty>) {
112
+ const title = Object.values(properties).find(
113
+ (property) => property.type === "title",
114
+ );
115
+ return title ? plainText(title.title) || "Untitled" : "Untitled";
116
+ }
117
+
118
+ export async function readNotionDatabaseSource(args: {
119
+ sourceTable: string;
120
+ limit: number;
121
+ offset: number;
122
+ fullRefresh?: boolean;
123
+ }): Promise<ContentDatabaseSourceReadResult> {
124
+ if (args.offset !== 0) {
125
+ throw new Error(
126
+ "Notion database sources use cursor pagination; numeric offsets are not supported.",
127
+ );
128
+ }
129
+ const userEmail = getRequestUserEmail();
130
+ if (!userEmail)
131
+ throw new Error("Notion database access requires a signed-in user.");
132
+ const connection = await getNotionConnectionForOwner(userEmail);
133
+ if (!connection)
134
+ throw new Error("Connect Notion before attaching a Notion database.");
135
+
136
+ const dataSource = await notionFetch<{
137
+ id: string;
138
+ title?: Array<{ plain_text?: string }>;
139
+ properties?: Record<string, NotionProperty>;
140
+ }>(
141
+ `/data_sources/${encodeURIComponent(args.sourceTable)}`,
142
+ connection.accessToken,
143
+ );
144
+ const fields: BuilderCmsModelFieldSummary[] = Object.entries(
145
+ dataSource.properties ?? {},
146
+ ).map(([name, property]) => ({
147
+ name: property.id || name,
148
+ label: property.name || name,
149
+ type: sourceFieldType(property.type ?? "unknown"),
150
+ required: false,
151
+ }));
152
+
153
+ const entries: BuilderCmsSourceEntry[] = [];
154
+ let cursor: string | undefined;
155
+ let hasMore = false;
156
+ const maxPages = args.fullRefresh ? NOTION_SOURCE_MAX_PAGES : 1;
157
+ for (let page = 0; page < maxPages; page++) {
158
+ const response = await notionFetch<{
159
+ results?: NotionPage[];
160
+ has_more?: boolean;
161
+ next_cursor?: string | null;
162
+ }>(
163
+ `/data_sources/${encodeURIComponent(args.sourceTable)}/query`,
164
+ connection.accessToken,
165
+ {
166
+ method: "POST",
167
+ body: JSON.stringify({
168
+ page_size: Math.min(NOTION_SOURCE_PAGE_SIZE, args.limit),
169
+ ...(cursor ? { start_cursor: cursor } : {}),
170
+ }),
171
+ },
172
+ );
173
+ for (const result of response.results ?? []) {
174
+ if (result.object && result.object !== "page") continue;
175
+ const properties = result.properties ?? {};
176
+ entries.push({
177
+ id: result.id,
178
+ model: args.sourceTable,
179
+ title: titleFromProperties(properties),
180
+ urlPath: result.url ?? "",
181
+ updatedAt: result.last_edited_time ?? "",
182
+ sourceValues: Object.fromEntries(
183
+ Object.entries(properties).map(([name, property]) => [
184
+ property.id || name,
185
+ propertyValue(property),
186
+ ]),
187
+ ),
188
+ });
189
+ }
190
+ hasMore = response.has_more === true && !!response.next_cursor;
191
+ cursor = response.next_cursor ?? undefined;
192
+ if (!hasMore || entries.length >= args.limit) break;
193
+ }
194
+
195
+ const fetchedAt = new Date().toISOString();
196
+ return {
197
+ state: "live",
198
+ entries: entries.slice(0, args.limit),
199
+ fields,
200
+ fetchedAt,
201
+ message: hasMore
202
+ ? `Loaded a bounded Notion snapshot; more rows remain after cursor ${cursor ? "present" : "missing"}.`
203
+ : null,
204
+ metadata: {
205
+ provider: "notion",
206
+ dataSourceId: dataSource.id,
207
+ dataSourceName: plainText(dataSource.title) || "Notion database",
208
+ hasMore,
209
+ nextCursor: cursor ?? null,
210
+ fetchedRowCount: Math.min(entries.length, args.limit),
211
+ unsupportedPropertyCount: fields.filter((field) =>
212
+ field.type.startsWith("unsupported:"),
213
+ ).length,
214
+ },
215
+ };
216
+ }
@@ -0,0 +1,76 @@
1
+ // Serializes "read MAX(position) among siblings, then insert/update at
2
+ // MAX+1" sequences so two concurrent writers (agent + human, two browser
3
+ // tabs, an import job racing a manual add) never read the same MAX and
4
+ // persist duplicate `position` values. `position` columns in this app have
5
+ // no unique/sequence constraint (see templates/content/server/db/schema.ts),
6
+ // so ordering is only as stable as the read-then-insert stays atomic.
7
+ //
8
+ // Mirrors the per-deck lock in templates/slides/actions/patch-deck.ts: an
9
+ // in-process, globalThis-keyed promise chain per scope key. This only
10
+ // serializes writers within THIS process, but that is sufficient here
11
+ // because every write path already goes through the app's own action/route
12
+ // layer in the same server process — it is not a substitute for a DB-level
13
+ // unique constraint, but it closes the read-then-write race that produces
14
+ // duplicate positions today.
15
+ //
16
+ // Sites that already compute MAX(position) and insert inside the SAME
17
+ // `db.transaction()` (e.g. submit-content-database-form.ts,
18
+ // add-content-database-source-field-property.ts) do not need this helper —
19
+ // wrap new sites in the lock instead of introducing a second mechanism.
20
+
21
+ const LOCK_KEY = "__contentPositionLocks" as const;
22
+ type GlobalWithLocks = typeof globalThis & {
23
+ [LOCK_KEY]?: Map<string, Promise<unknown>>;
24
+ };
25
+ const globalRef = globalThis as GlobalWithLocks;
26
+ if (!globalRef[LOCK_KEY]) {
27
+ globalRef[LOCK_KEY] = new Map<string, Promise<unknown>>();
28
+ }
29
+ const positionLocks: Map<string, Promise<unknown>> = globalRef[LOCK_KEY]!;
30
+
31
+ /** Run `fn` serialized against any other call using the same `scopeKey`. */
32
+ export function withPositionLock<T>(
33
+ scopeKey: string,
34
+ fn: () => Promise<T>,
35
+ ): Promise<T> {
36
+ const prev = positionLocks.get(scopeKey) ?? Promise.resolve();
37
+ const next = prev.then(fn, fn);
38
+ positionLocks.set(scopeKey, next);
39
+ next
40
+ .finally(() => {
41
+ if (positionLocks.get(scopeKey) === next) positionLocks.delete(scopeKey);
42
+ })
43
+ .catch(() => {});
44
+ return next;
45
+ }
46
+
47
+ /**
48
+ * Lock scope for `documents.position` siblings under one owner + parent
49
+ * (or one owner's root level when `parentId` is null/undefined). Every call
50
+ * site that computes MAX(position) over `documents` scoped by
51
+ * (ownerEmail, parentId) should share this key so concurrent inserts under
52
+ * the same parent — regardless of which action/route triggers them — are
53
+ * serialized against each other.
54
+ */
55
+ export function documentsPositionScope(
56
+ ownerEmail: string,
57
+ parentId: string | null | undefined,
58
+ ): string {
59
+ return `documents:${ownerEmail}:${parentId ?? "root"}`;
60
+ }
61
+
62
+ /**
63
+ * Lock scope for `content_database_items.position` rows within one
64
+ * database.
65
+ */
66
+ export function databaseItemsPositionScope(databaseId: string): string {
67
+ return `contentDatabaseItems:${databaseId}`;
68
+ }
69
+
70
+ /**
71
+ * Lock scope for `document_property_definitions.position` rows within one
72
+ * database.
73
+ */
74
+ export function propertyDefinitionsPositionScope(databaseId: string): string {
75
+ return `documentPropertyDefinitions:${databaseId}`;
76
+ }