@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
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Shared file-walking / offset helpers used by every guard in this
3
+ * directory. Ported (behavior-for-behavior) from the identical helpers
4
+ * duplicated across `scripts/guard-*.mjs` — see each guard file for the
5
+ * specific `scripts/guard-<name>.mjs` it was derived from.
6
+ *
7
+ * The original scripts walk directories with `fs/promises` async
8
+ * generators. Guard functions here are synchronous pure functions (per the
9
+ * plan's `scan<Name>(options): GuardResult` contract), so this module uses
10
+ * `fs.readdirSync` instead — same skip-dir semantics, no behavior change.
11
+ */
12
+
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+
16
+ /** Directories skipped by every guard's repo/app walk. */
17
+ export const DEFAULT_SKIP_DIRS = new Set([
18
+ "node_modules",
19
+ ".git",
20
+ "dist",
21
+ "build",
22
+ ".next",
23
+ ".nuxt",
24
+ ".output",
25
+ ".cache",
26
+ ".turbo",
27
+ ".netlify",
28
+ ".vercel",
29
+ ".wrangler",
30
+ ".react-router",
31
+ ".generated",
32
+ ".claude",
33
+ "out",
34
+ "coverage",
35
+ ]);
36
+
37
+ /** Recursively walk `dir`, yielding absolute file paths. Skips any
38
+ * directory whose basename is in `skipDirs`. */
39
+ export function* walk(
40
+ dir: string,
41
+ skipDirs: Set<string> = DEFAULT_SKIP_DIRS,
42
+ ): Generator<string> {
43
+ let entries: fs.Dirent[];
44
+ try {
45
+ entries = fs.readdirSync(dir, { withFileTypes: true });
46
+ } catch {
47
+ return;
48
+ }
49
+ for (const entry of entries) {
50
+ const full = path.join(dir, entry.name);
51
+ if (entry.isDirectory()) {
52
+ if (skipDirs.has(entry.name)) continue;
53
+ yield* walk(full, skipDirs);
54
+ } else if (entry.isFile()) {
55
+ yield full;
56
+ }
57
+ }
58
+ }
59
+
60
+ export function lineColForOffset(
61
+ contents: string,
62
+ offset: number,
63
+ ): { line: number; col: number } {
64
+ let line = 1;
65
+ let lineStart = 0;
66
+ for (let i = 0; i < offset; i++) {
67
+ if (contents.charCodeAt(i) === 10) {
68
+ line++;
69
+ lineStart = i + 1;
70
+ }
71
+ }
72
+ return { line, col: offset - lineStart + 1 };
73
+ }
74
+
75
+ export function isCommentLine(lineText: string): boolean {
76
+ const trimmed = lineText.trimStart();
77
+ return (
78
+ trimmed.startsWith("*") ||
79
+ trimmed.startsWith("//") ||
80
+ trimmed.startsWith("/*")
81
+ );
82
+ }
83
+
84
+ export function relPosix(root: string, file: string): string {
85
+ return path.relative(root, file).replaceAll("\\", "/");
86
+ }
87
+
88
+ export function readFileSafe(file: string): string | null {
89
+ try {
90
+ return fs.readFileSync(file, "utf8");
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+
96
+ /** True if `rel` matches an exact-path entry in `extraExemptPaths`
97
+ * (posix-relative, exact string match — the same shape the exempt-path
98
+ * sets used in the original monorepo guards). */
99
+ export function isExtraExempt(
100
+ rel: string,
101
+ extraExemptPaths: string[] | undefined,
102
+ ): boolean {
103
+ if (!extraExemptPaths || extraExemptPaths.length === 0) return false;
104
+ return extraExemptPaths.includes(rel);
105
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shared types for `agent-native doctor`'s guard functions.
3
+ *
4
+ * Each guard is a pure function `scan<Name>(options): GuardResult` that
5
+ * scans a single app root and returns a flat list of findings. Guards do
6
+ * not print, exit, or read `agent-native.json` themselves — that
7
+ * orchestration lives in `../cli/doctor.ts` so the functions stay pure and
8
+ * independently testable/importable (see the `./guards` package export).
9
+ */
10
+
11
+ export interface GuardFinding {
12
+ /** Path to the offending file, relative to the scanned root. */
13
+ file: string;
14
+ /** 1-based line number of the offending line. */
15
+ line: number;
16
+ /** Human-readable explanation of the finding. */
17
+ message: string;
18
+ }
19
+
20
+ export interface GuardResult {
21
+ /** Stable guard identifier, e.g. "no-drizzle-push". Matches the name used
22
+ * by `--only` and `agent-native.json`'s `doctor.disabledGuards`. */
23
+ name: string;
24
+ findings: GuardFinding[];
25
+ }
26
+
27
+ export interface GuardScanOptions {
28
+ /** Absolute path to the app root to scan. */
29
+ root: string;
30
+ }
@@ -63,8 +63,16 @@ export async function acceptPendingInvitationsForEmail(
63
63
  });
64
64
  if (existing.rows.length === 0) {
65
65
  const role = inv.role === "admin" ? "admin" : "member";
66
+ // The SELECT above is a cheap pre-check, not a correctness guard —
67
+ // two concurrent acceptances (e.g. a retried signup hook) can both
68
+ // pass it before either INSERT commits. `ON CONFLICT (org_id,
69
+ // LOWER(email)) DO NOTHING` targets the unique expression index
70
+ // added in migrations.ts (org-members-unique-lower-email-idx) so the
71
+ // race's loser is a silent no-op instead of a thrown unique
72
+ // constraint violation or a duplicate row.
66
73
  await db.execute({
67
- sql: `INSERT INTO org_members (id, org_id, email, role, joined_at) VALUES (?, ?, ?, ?, ?)`,
74
+ sql: `INSERT INTO org_members (id, org_id, email, role, joined_at) VALUES (?, ?, ?, ?, ?)
75
+ ON CONFLICT (org_id, LOWER(email)) DO NOTHING`,
68
76
  args: [nanoid(), inv.orgId, email, role, Date.now()],
69
77
  });
70
78
  }
@@ -61,4 +61,45 @@ export const ORG_MIGRATIONS = [
61
61
  version: 1008,
62
62
  sql: `CREATE INDEX IF NOT EXISTS organizations_lower_allowed_domain_idx ON organizations (LOWER(allowed_domain))`,
63
63
  },
64
+ {
65
+ // De-dup pass ahead of the unique index below. `org_members` has always
66
+ // had a `UNIQUE(org_id, email)` constraint (see v1002), but that's an
67
+ // exact-string match — callers that insert a session's raw-case email
68
+ // (e.g. handlers.ts's acceptInvitationHandler) can still create a
69
+ // second row for the same person under a different case, and every
70
+ // membership *read* in this module already matches case-insensitively
71
+ // via `LOWER(email)`. This keeps exactly one row per (org_id,
72
+ // LOWER(email)) — the row with the oldest `joined_at` (ties broken by
73
+ // `id` for determinism) — by deleting any row for which a strictly
74
+ // "older" row exists in the same group. Portable across SQLite and
75
+ // Postgres (plain correlated EXISTS subquery, no window functions).
76
+ // Must run before the unique index below or that CREATE would fail on
77
+ // any database that already has case-variant duplicates.
78
+ version: 1009,
79
+ name: "org-members-dedupe-lower-email",
80
+ sql: `DELETE FROM org_members
81
+ WHERE EXISTS (
82
+ SELECT 1 FROM org_members older
83
+ WHERE older.org_id = org_members.org_id
84
+ AND LOWER(older.email) = LOWER(org_members.email)
85
+ AND (
86
+ older.joined_at < org_members.joined_at
87
+ OR (older.joined_at = org_members.joined_at AND older.id < org_members.id)
88
+ )
89
+ )`,
90
+ },
91
+ {
92
+ // Closes the TOCTOU window in `acceptPendingInvitationsForEmail`
93
+ // (org/accept-pending.ts): a SELECT-then-INSERT check with no unique
94
+ // constraint standing behind the case-insensitive comparison every
95
+ // reader uses. Without this, two concurrent acceptances (e.g. a
96
+ // retried signup hook) can both pass the SELECT and both INSERT,
97
+ // producing duplicate membership rows. This expression index makes
98
+ // (org_id, LOWER(email)) unique at the database level so an
99
+ // `ON CONFLICT` insert (or a raw duplicate insert) is rejected
100
+ // instead of silently creating a second row.
101
+ version: 1010,
102
+ name: "org-members-unique-lower-email-idx",
103
+ sql: `CREATE UNIQUE INDEX IF NOT EXISTS org_members_org_lower_email_uidx ON org_members (org_id, LOWER(email))`,
104
+ },
64
105
  ];
@@ -0,0 +1 @@
1
+ export * from "./manifest.js";
@@ -0,0 +1,120 @@
1
+ export const AGENT_NATIVE_PACKAGE_MANIFEST_VERSION = 1 as const;
2
+
3
+ export interface AgentNativePackageManifest {
4
+ manifestVersion: typeof AGENT_NATIVE_PACKAGE_MANIFEST_VERSION;
5
+ name: string;
6
+ actions: string[];
7
+ schemaEntryPoint: string;
8
+ docs: {
9
+ llms: string;
10
+ llmsFull: string;
11
+ skills: string[];
12
+ };
13
+ requiredSecrets: { key: string; label: string; optional?: boolean }[];
14
+ peerProviders: string[];
15
+ eject?: {
16
+ sourceRoot: string;
17
+ targetDirectory: string;
18
+ };
19
+ }
20
+
21
+ export function assertAgentNativePackageManifest(
22
+ value: unknown,
23
+ ): asserts value is AgentNativePackageManifest {
24
+ const manifest = value as Partial<AgentNativePackageManifest> | null;
25
+ if (!manifest || typeof manifest !== "object") {
26
+ throw new Error("Package manifest must be an object");
27
+ }
28
+ if (manifest.manifestVersion !== AGENT_NATIVE_PACKAGE_MANIFEST_VERSION) {
29
+ throw new Error(
30
+ `Unsupported package manifest version ${String(manifest.manifestVersion)}`,
31
+ );
32
+ }
33
+ if (!manifest.name || !manifest.schemaEntryPoint) {
34
+ throw new Error("Package manifest is missing name or schemaEntryPoint");
35
+ }
36
+ if (!/^(@[a-z0-9._-]+\/)?[a-z0-9._-]+$/.test(manifest.name)) {
37
+ throw new Error(`Invalid package manifest name: ${manifest.name}`);
38
+ }
39
+ if (
40
+ !manifest.schemaEntryPoint.startsWith(`${manifest.name}/`) ||
41
+ !isSafeSpecifier(manifest.schemaEntryPoint)
42
+ ) {
43
+ throw new Error("Package manifest schemaEntryPoint is unsafe");
44
+ }
45
+ if (
46
+ !Array.isArray(manifest.actions) ||
47
+ !manifest.docs ||
48
+ !Array.isArray(manifest.docs.skills) ||
49
+ !Array.isArray(manifest.requiredSecrets) ||
50
+ !Array.isArray(manifest.peerProviders)
51
+ ) {
52
+ throw new Error("Package manifest contribution lists are invalid");
53
+ }
54
+ for (const action of manifest.actions) {
55
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(action)) {
56
+ throw new Error(`Invalid package action name: ${action}`);
57
+ }
58
+ }
59
+ for (const skill of manifest.docs.skills) {
60
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(skill)) {
61
+ throw new Error(`Invalid package skill name: ${skill}`);
62
+ }
63
+ }
64
+ for (const docPath of [manifest.docs.llms, manifest.docs.llmsFull]) {
65
+ if (!isSafeRelativePath(docPath)) {
66
+ throw new Error(`Unsafe package documentation path: ${docPath}`);
67
+ }
68
+ }
69
+ for (const secret of manifest.requiredSecrets) {
70
+ if (
71
+ !secret ||
72
+ !/^[A-Z][A-Z0-9_]*$/.test(secret.key) ||
73
+ typeof secret.label !== "string" ||
74
+ secret.label.length === 0 ||
75
+ secret.label.length > 120 ||
76
+ (secret.optional !== undefined && typeof secret.optional !== "boolean")
77
+ ) {
78
+ throw new Error("Invalid package requiredSecrets entry");
79
+ }
80
+ }
81
+ for (const provider of manifest.peerProviders) {
82
+ if (
83
+ typeof provider !== "string" ||
84
+ !/^[a-z0-9][a-z0-9-]*$/.test(provider)
85
+ ) {
86
+ throw new Error(`Invalid package peer provider: ${String(provider)}`);
87
+ }
88
+ }
89
+ if (
90
+ manifest.eject &&
91
+ (!isSafeRelativePath(manifest.eject.sourceRoot) ||
92
+ !isSafeRelativePath(manifest.eject.targetDirectory))
93
+ ) {
94
+ throw new Error("Package eject paths must be safe relative paths");
95
+ }
96
+ }
97
+
98
+ function isSafeRelativePath(value: unknown): value is string {
99
+ if (typeof value !== "string" || value.length === 0 || value.includes("\\")) {
100
+ return false;
101
+ }
102
+ if (
103
+ value.includes("\0") ||
104
+ value.startsWith("/") ||
105
+ pathSegments(value).includes("..")
106
+ ) {
107
+ return false;
108
+ }
109
+ return !value.split("/").includes("");
110
+ }
111
+
112
+ function isSafeSpecifier(value: string): boolean {
113
+ return (
114
+ /^[A-Za-z0-9@._/-]+$/.test(value) && !pathSegments(value).includes("..")
115
+ );
116
+ }
117
+
118
+ function pathSegments(value: string): string[] {
119
+ return value.split("/");
120
+ }
@@ -64,6 +64,12 @@ export interface UpsertCustomProviderArgs {
64
64
  allowedHostSuffixes?: string[];
65
65
  defaultHeaders?: Record<string, string>;
66
66
  notes?: string;
67
+ /**
68
+ * Caller's org role in the target org (`scopeId` when `scope === "org"`),
69
+ * resolved by the caller (e.g. `provider-api-register.ts`) before invoking
70
+ * this function. Required — see `assertCanMutateCustomProviderScope`.
71
+ */
72
+ orgRole: string | null;
67
73
  }
68
74
 
69
75
  // ---------------------------------------------------------------------------
@@ -258,6 +264,53 @@ export function validateAllowedHostSuffixes(suffixes: string[]): string[] {
258
264
  return normalized;
259
265
  }
260
266
 
267
+ // ---------------------------------------------------------------------------
268
+ // Authorization
269
+ // ---------------------------------------------------------------------------
270
+
271
+ /**
272
+ * Thrown by `assertCanMutateCustomProviderScope` when the caller is not
273
+ * authorized to mutate a scoped custom provider row.
274
+ */
275
+ export class CustomProviderAuthError extends Error {
276
+ constructor(
277
+ readonly statusCode: number,
278
+ message: string,
279
+ ) {
280
+ super(message);
281
+ this.name = "CustomProviderAuthError";
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Defense-in-depth authorization guard for custom-provider mutations. Mirrors
287
+ * `assertCanMutateScope` in `server/scoped-key-storage.ts`: user-scoped writes
288
+ * need no role check (each user only ever mutates their own row), but
289
+ * org-scoped writes require the caller to be an owner or admin of the target
290
+ * org (`scopeId`) — otherwise a plain member could repoint an org-wide
291
+ * provider's base URL or attach a different credential key name that other
292
+ * members' agents then trust. `scopeId` is accepted (unused today) for
293
+ * signature parity with `assertCanMutateScope` in case a future scope value
294
+ * needs it (e.g. a solo-workspace carve-out).
295
+ *
296
+ * The only caller today is `provider-api-register.ts`, which resolves
297
+ * `orgRole` itself and enforces this same check before calling in; this
298
+ * function is the second, harder-to-bypass layer so a future caller can't
299
+ * reintroduce the gap by forgetting to check.
300
+ */
301
+ export function assertCanMutateCustomProviderScope(
302
+ scope: CustomProviderScope,
303
+ _scopeId: string,
304
+ orgRole: string | null,
305
+ ): void {
306
+ if (scope === "user") return;
307
+ if (orgRole === "owner" || orgRole === "admin") return;
308
+ throw new CustomProviderAuthError(
309
+ 403,
310
+ "Only organization owners and admins can register or modify org-scoped custom providers.",
311
+ );
312
+ }
313
+
261
314
  // ---------------------------------------------------------------------------
262
315
  // CRUD
263
316
  // ---------------------------------------------------------------------------
@@ -269,6 +322,7 @@ export function validateAllowedHostSuffixes(suffixes: string[]): string[] {
269
322
  export async function upsertCustomProvider(
270
323
  args: UpsertCustomProviderArgs,
271
324
  ): Promise<string> {
325
+ assertCanMutateCustomProviderScope(args.scope, args.scopeId, args.orgRole);
272
326
  await ensureTable();
273
327
  validateProviderId(args.id);
274
328
  validateAuth(args.auth);
@@ -333,7 +387,9 @@ export async function deleteCustomProvider(
333
387
  scope: CustomProviderScope,
334
388
  scopeId: string,
335
389
  id: string,
390
+ orgRole: string | null,
336
391
  ): Promise<boolean> {
392
+ assertCanMutateCustomProviderScope(scope, scopeId, orgRole);
337
393
  await ensureTable();
338
394
  const client = getDbExec();
339
395
  const { rowsAffected } = await client.execute({
@@ -47,6 +47,8 @@ export {
47
47
  listCustomProviders,
48
48
  getCustomProvider,
49
49
  validateCustomBaseUrl,
50
+ assertCanMutateCustomProviderScope,
51
+ CustomProviderAuthError,
50
52
  } from "./custom-registry.js";
51
53
 
52
54
  export const PROVIDER_API_IDS = [
@@ -142,37 +142,57 @@ export async function writeAppSecret(args: WriteSecretArgs): Promise<string> {
142
142
  const client = getDbExec();
143
143
  const now = Date.now();
144
144
  const encrypted = encryptValue(value);
145
+ const id = randomUUID();
145
146
 
146
- // Upsert by (scope, scope_id, key). Keep the existing row's id on update so
147
- // references stay stable.
148
- const { rows } = await client.execute({
149
- sql: `SELECT id FROM app_secrets WHERE scope = ? AND scope_id = ? AND key = ?`,
150
- args: [scope, scopeId, key],
151
- });
152
- if (rows.length > 0) {
153
- const id = rows[0].id as string;
154
- await client.execute({
155
- sql: `UPDATE app_secrets SET encrypted_value = ?, description = ?, url_allowlist = ?, updated_at = ? WHERE id = ?`,
156
- args: [encrypted, description ?? null, urlAllowlist ?? null, now, id],
147
+ // Atomic upsert by (scope, scope_id, key). Previously this was a
148
+ // SELECT-then-branch (UPDATE if found, else INSERT): under concurrent
149
+ // writers for the same key both could see "no row" and both attempt
150
+ // INSERT, and the loser threw a raw UNIQUE(scope, scope_id, key)
151
+ // constraint violation (a user-facing 500) instead of updating. A single
152
+ // `INSERT ... ON CONFLICT DO UPDATE` closes that window — it's one
153
+ // statement, so there's no gap between "check" and "act". `id` is
154
+ // deliberately left out of the `DO UPDATE SET` list so an existing row
155
+ // keeps its original id (any stored references stay stable); only a
156
+ // genuinely new row gets the freshly generated `id`. This syntax is
157
+ // portable across SQLite (UPSERT since 3.24) and Postgres.
158
+ const upsertSql = `INSERT INTO app_secrets (id, scope, scope_id, key, encrypted_value, description, url_allowlist, created_at, updated_at)
159
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
160
+ ON CONFLICT (scope, scope_id, key) DO UPDATE SET
161
+ encrypted_value = excluded.encrypted_value,
162
+ description = excluded.description,
163
+ url_allowlist = excluded.url_allowlist,
164
+ updated_at = excluded.updated_at`;
165
+ const upsertArgs = [
166
+ id,
167
+ scope,
168
+ scopeId,
169
+ key,
170
+ encrypted,
171
+ description ?? null,
172
+ urlAllowlist ?? null,
173
+ now,
174
+ now,
175
+ ];
176
+
177
+ if (isPostgres()) {
178
+ const { rows } = await client.execute({
179
+ sql: `${upsertSql} RETURNING id`,
180
+ args: upsertArgs,
157
181
  });
158
- return id;
182
+ return String(rows[0]?.id ?? id);
159
183
  }
160
- const id = randomUUID();
161
- await client.execute({
162
- sql: `INSERT INTO app_secrets (id, scope, scope_id, key, encrypted_value, description, url_allowlist, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
163
- args: [
164
- id,
165
- scope,
166
- scopeId,
167
- key,
168
- encrypted,
169
- description ?? null,
170
- urlAllowlist ?? null,
171
- now,
172
- now,
173
- ],
184
+
185
+ // SQLite: RETURNING support varies across better-sqlite3/libsql builds, so
186
+ // (matching the convention elsewhere in this codebase, e.g.
187
+ // integrations/pending-tasks-store.ts) re-read the id afterward instead of
188
+ // relying on it. The row is guaranteed to exist at this point, so this is
189
+ // a plain lookup rather than a TOCTOU-prone gate on the write itself.
190
+ await client.execute({ sql: upsertSql, args: upsertArgs });
191
+ const { rows } = await client.execute({
192
+ sql: `SELECT id FROM app_secrets WHERE scope = ? AND scope_id = ? AND key = ? LIMIT 1`,
193
+ args: [scope, scopeId, key],
174
194
  });
175
- return id;
195
+ return String(rows[0]?.id ?? id);
176
196
  }
177
197
 
178
198
  export interface ReadSecretResult {
@@ -606,6 +606,10 @@ export async function mergeCoreSharingActions(
606
606
  () => import("../audit/actions/list-audit-events.js"),
607
607
  ],
608
608
  ["get-audit-event", () => import("../audit/actions/get-audit-event.js")],
609
+ [
610
+ "export-audit-events",
611
+ () => import("../audit/actions/export-audit-events.js"),
612
+ ],
609
613
  // History kit — reusable version snapshots and restore surface.
610
614
  [
611
615
  "create-resource-version",
@@ -8,6 +8,7 @@ import {
8
8
  getMethod,
9
9
  getQuery,
10
10
  getHeader,
11
+ getRequestIP,
11
12
  type H3Event,
12
13
  } from "h3";
13
14
 
@@ -19,7 +20,8 @@ import {
19
20
  } from "../a2a/artifact-response.js";
20
21
  import {
21
22
  hasConfiguredA2ASecret,
22
- isA2AProductionRuntime,
23
+ isLoopbackAddress,
24
+ isTrustedLocalRuntime,
23
25
  } from "../a2a/auth-policy.js";
24
26
  import {
25
27
  applyAgentTextEventToBuffer,
@@ -1065,6 +1067,10 @@ function appStateKeyForBrowserTab(key: string, browserTabId: unknown): string {
1065
1067
  */
1066
1068
  const generateTitleRateLimit = new Map<string, number[]>();
1067
1069
 
1070
+ /** Only sweep drained rate-limit entries once the map grows past this size,
1071
+ * so the common small-map case stays O(1). */
1072
+ const RATE_LIMIT_SWEEP_THRESHOLD = 1000;
1073
+
1068
1074
  /**
1069
1075
  * Creates the `set-search-params` / `set-url-path` tools. Writes a one-shot
1070
1076
  * URL command to application_state; the client's URLSync component applies
@@ -6551,12 +6557,17 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
6551
6557
  setResponseStatus(event, 401);
6552
6558
  return { error: "Invalid or expired processor token" };
6553
6559
  }
6554
- } else if (isA2AProductionRuntime()) {
6555
- setResponseStatus(event, 503);
6556
- return {
6557
- error:
6558
- "Agent Teams processor not configured — set A2A_SECRET on this deployment.",
6559
- };
6560
+ } else {
6561
+ const loopback = isLoopbackAddress(
6562
+ getRequestIP(event, { xForwardedFor: false }),
6563
+ );
6564
+ if (!isTrustedLocalRuntime({ loopback })) {
6565
+ setResponseStatus(event, 503);
6566
+ return {
6567
+ error:
6568
+ "Agent Teams processor not configured — set A2A_SECRET on this deployment (or A2A_ALLOW_UNSIGNED_INTERNAL=1 for trusted local dev).",
6569
+ };
6570
+ }
6560
6571
  }
6561
6572
 
6562
6573
  try {
@@ -7224,6 +7235,11 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7224
7235
  setResponseHeader(event, "Content-Type", "application/x-ndjson");
7225
7236
  setResponseHeader(event, "Cache-Control", "no-cache");
7226
7237
 
7238
+ // Lets `cancel()` signal in-flight source work to stop early on
7239
+ // client disconnect, instead of only being noticed the next time
7240
+ // `flush()`'s `controller.enqueue` throws.
7241
+ const mentionsAbort = new AbortController();
7242
+
7227
7243
  const stream = new ReadableStream({
7228
7244
  start(controller) {
7229
7245
  return runWithRequestContext(
@@ -7235,7 +7251,9 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7235
7251
  );
7236
7252
  },
7237
7253
  cancel() {
7238
- // Client disconnected — stop enqueuing
7254
+ // Client disconnected — stop enqueuing and let in-flight source
7255
+ // work short-circuit instead of running to completion unseen.
7256
+ mentionsAbort.abort();
7239
7257
  },
7240
7258
  });
7241
7259
 
@@ -7246,10 +7264,13 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7246
7264
  ) {
7247
7265
  const MAX_RESULTS = 50;
7248
7266
  let totalSent = 0;
7249
- let cancelled = false;
7267
+ let cancelled = mentionsAbort.signal.aborted;
7250
7268
 
7251
7269
  const flush = (batch: MentionItemResponse[]) => {
7252
- if (cancelled) return;
7270
+ if (cancelled || mentionsAbort.signal.aborted) {
7271
+ cancelled = true;
7272
+ return;
7273
+ }
7253
7274
  const filtered = batch.filter(matchesQuery);
7254
7275
  if (filtered.length === 0) return;
7255
7276
  const remaining = MAX_RESULTS - totalSent;
@@ -7332,6 +7353,8 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7332
7353
 
7333
7354
  // 3. Custom mention providers (each flushes independently)
7334
7355
  for (const [key, provider] of Object.entries(mentionProviders)) {
7356
+ // Client already disconnected — don't spawn more provider work.
7357
+ if (mentionsAbort.signal.aborted) break;
7335
7358
  sources.push(
7336
7359
  (async () => {
7337
7360
  try {
@@ -7419,7 +7442,9 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7419
7442
  );
7420
7443
 
7421
7444
  await Promise.all(sources);
7422
- if (!cancelled) controller.close();
7445
+ if (!cancelled && !mentionsAbort.signal.aborted) {
7446
+ controller.close();
7447
+ }
7423
7448
  }
7424
7449
  }),
7425
7450
  );
@@ -7450,6 +7475,19 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7450
7475
  recent.push(now);
7451
7476
  generateTitleRateLimit.set(ownerEmail, recent);
7452
7477
 
7478
+ // Opportunistic eviction: keep the map from growing unbounded by
7479
+ // the count of distinct users on a long-lived process. Drop any
7480
+ // key whose window has fully drained (no timestamps within the
7481
+ // rolling window).
7482
+ if (generateTitleRateLimit.size > RATE_LIMIT_SWEEP_THRESHOLD) {
7483
+ for (const [email, times] of generateTitleRateLimit) {
7484
+ if (email === ownerEmail) continue;
7485
+ if (times.every((t) => now - t >= limitWindowMs)) {
7486
+ generateTitleRateLimit.delete(email);
7487
+ }
7488
+ }
7489
+ }
7490
+
7453
7491
  const body = await readBody(event);
7454
7492
  const message = body?.message;
7455
7493
  if (!message || typeof message !== "string") {
@@ -8438,10 +8476,14 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
8438
8476
 
8439
8477
  // Validate + HMAC-authenticate the self-dispatch and prepare the
8440
8478
  // background-worker body. Pure decision (unit-tested in
8441
- // durable-background.spec.ts); the route only wires it to h3.
8479
+ // durable-background.spec.ts); the route only wires it to h3. This
8480
+ // handler DOES have the h3 event, so it can see the real socket
8481
+ // peer — thread the loopback signal through so unsigned local-dev
8482
+ // self-dispatch (no A2A_SECRET) still works over 127.0.0.1/::1.
8442
8483
  const prepared = prepareProcessRunRequest(
8443
8484
  processBody,
8444
8485
  getHeader(event, "authorization"),
8486
+ isLoopbackAddress(getRequestIP(event, { xForwardedFor: false })),
8445
8487
  );
8446
8488
  if (!prepared.ok) {
8447
8489
  // DIAGNOSTIC: record the auth/validation failure ONTO the run