@agent-native/core 0.69.0 → 0.70.1

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 (336) hide show
  1. package/README.md +15 -15
  2. package/corpus/README.md +2 -2
  3. package/corpus/core/CHANGELOG.md +109 -0
  4. package/corpus/core/docs/design/durable-agent-runs.md +217 -0
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/action.ts +89 -0
  7. package/corpus/core/src/agent/model-config.ts +0 -2
  8. package/corpus/core/src/agent/run-loop-with-resume.ts +48 -0
  9. package/corpus/core/src/changelog/parse.ts +245 -0
  10. package/corpus/core/src/cli/changelog.ts +199 -0
  11. package/corpus/core/src/cli/index.ts +19 -0
  12. package/corpus/core/src/cli/recap.ts +24 -1
  13. package/corpus/core/src/cli/skills.ts +74 -47
  14. package/corpus/core/src/client/AgentPanel.tsx +5 -3
  15. package/corpus/core/src/client/AssistantChat.tsx +8 -0
  16. package/corpus/core/src/client/CommandMenu.tsx +183 -75
  17. package/corpus/core/src/client/agent-chat-adapter.ts +44 -7
  18. package/corpus/core/src/client/analytics.ts +181 -0
  19. package/corpus/core/src/client/changelog/Changelog.tsx +321 -0
  20. package/corpus/core/src/client/client-surface.ts +41 -0
  21. package/corpus/core/src/client/composer/TiptapComposer.tsx +123 -12
  22. package/corpus/core/src/client/feedback-context.ts +4 -0
  23. package/corpus/core/src/client/index.ts +12 -0
  24. package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +54 -0
  25. package/corpus/core/src/client/settings/useBuilderStatus.ts +82 -20
  26. package/corpus/core/src/client/sse-event-processor.ts +7 -1
  27. package/corpus/core/src/mcp/build-server.ts +57 -1
  28. package/corpus/core/src/mcp/embed-app.ts +197 -7
  29. package/corpus/core/src/mcp/oauth-route.ts +109 -13
  30. package/corpus/core/src/server/attribution.ts +211 -0
  31. package/corpus/core/src/server/auth.ts +6 -0
  32. package/corpus/core/src/server/better-auth-instance.ts +49 -5
  33. package/corpus/core/src/server/builder-browser.ts +27 -1
  34. package/corpus/core/src/server/core-routes-plugin.ts +136 -0
  35. package/corpus/core/src/server/google-oauth.ts +19 -6
  36. package/corpus/core/src/server/onboarding-html.ts +35 -0
  37. package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
  38. package/corpus/core/src/styles/agent-conversation.css +21 -0
  39. package/corpus/core/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  40. package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
  41. package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  42. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +22 -0
  43. package/corpus/templates/analytics/AGENTS.md +8 -1
  44. package/corpus/templates/analytics/CHANGELOG.md +10 -0
  45. package/corpus/templates/analytics/actions/install-dashboard-template.ts +99 -4
  46. package/corpus/templates/analytics/actions/open-traffic-dashboard.ts +1 -1
  47. package/corpus/templates/analytics/actions/update-dashboard.ts +39 -8
  48. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +30 -3
  49. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +180 -144
  50. package/corpus/templates/analytics/app/components/layout/Header.tsx +2 -3
  51. package/corpus/templates/analytics/app/components/layout/Layout.tsx +7 -5
  52. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +221 -215
  53. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +6 -6
  54. package/corpus/templates/analytics/app/lib/demo-dashboard-path.ts +1 -1
  55. package/corpus/templates/analytics/app/lib/last-opened.ts +2 -1
  56. package/corpus/templates/analytics/app/pages/Settings.tsx +4 -0
  57. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -3
  58. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +2 -2
  59. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -2
  60. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +101 -99
  61. package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +1 -1
  62. package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +18 -5
  63. package/corpus/templates/analytics/app/routes/catalog.tsx +1 -1
  64. package/corpus/templates/analytics/app/routes/dashboards.$id.tsx +9 -0
  65. package/corpus/templates/analytics/app/routes/traffic.tsx +1 -1
  66. package/corpus/templates/analytics/changelog/2026-06-23-long-dashboard-and-analysis-names-in-the-sidebar-now-truncat.md +6 -0
  67. package/corpus/templates/analytics/changelog/2026-06-23-open-any-dashboard-chart-full-screen-in-a-modal-from-the-pan.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-06-23-the-dashboards-and-analyses-sidebar-settings-buttons-now-sta.md +6 -0
  69. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +145 -0
  70. package/corpus/templates/analytics/server/db/index.ts +1 -1
  71. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +9 -2
  72. package/corpus/templates/analytics/server/lib/demo-dashboards.ts +1 -1
  73. package/corpus/templates/analytics/server/plugins/agent-chat.ts +2 -2
  74. package/corpus/templates/analytics/server/plugins/core-routes.ts +6 -6
  75. package/corpus/templates/assets/CHANGELOG.md +10 -0
  76. package/corpus/templates/assets/actions/generate-image.ts +24 -0
  77. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +2 -2
  78. package/corpus/templates/assets/app/global.css +17 -1
  79. package/corpus/templates/assets/app/root.tsx +7 -1
  80. package/corpus/templates/assets/app/routes/_index.tsx +94 -14
  81. package/corpus/templates/assets/app/routes/settings.tsx +6 -0
  82. package/corpus/templates/assets/changelog/2026-06-23-chat-dates-in-the-sidebar-no-longer-wrap-onto-two-lines.md +6 -0
  83. package/corpus/templates/assets/changelog/2026-06-23-clicking-image-video-or-refine-on-the-start-screen-now-drops.md +6 -0
  84. package/corpus/templates/assets/changelog/2026-06-23-pick-your-image-generation-model-right-from-the-chat-model-m.md +6 -0
  85. package/corpus/templates/brain/CHANGELOG.md +10 -0
  86. package/corpus/templates/brain/app/root.tsx +7 -1
  87. package/corpus/templates/brain/app/routes/settings.tsx +8 -1
  88. package/corpus/templates/calendar/CHANGELOG.md +10 -0
  89. package/corpus/templates/calendar/app/pages/Settings.tsx +4 -0
  90. package/corpus/templates/calendar/app/root.tsx +7 -1
  91. package/corpus/templates/chat/CHANGELOG.md +10 -0
  92. package/corpus/templates/chat/app/root.tsx +7 -1
  93. package/corpus/templates/clips/CHANGELOG.md +10 -0
  94. package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -20
  95. package/corpus/templates/clips/app/components/player/share-dialog.tsx +33 -7
  96. package/corpus/templates/clips/app/components/player/sign-in-prompt-dialog.tsx +8 -0
  97. package/corpus/templates/clips/app/components/player/video-player.tsx +150 -20
  98. package/corpus/templates/clips/app/components/recorder/countdown-overlay.tsx +72 -48
  99. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +7 -11
  100. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +31 -0
  101. package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +71 -0
  102. package/corpus/templates/clips/app/lib/countdown-audio-cue.ts +35 -17
  103. package/corpus/templates/clips/app/root.tsx +7 -1
  104. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +15 -1
  105. package/corpus/templates/clips/app/routes/record.tsx +78 -5
  106. package/corpus/templates/clips/app/routes/share.$shareId.tsx +86 -5
  107. package/corpus/templates/clips/changelog/2026-06-23-clips-shared-in-slack-no-longer-show-a-could-not-start-playb.md +6 -0
  108. package/corpus/templates/clips/changelog/2026-06-23-made-the-play-button-on-shared-and-embedded-clips-scale-with.md +6 -0
  109. package/corpus/templates/clips/changelog/2026-06-23-public-clip-share-links-now-show-whether-they-ll-play-inline.md +6 -0
  110. package/corpus/templates/clips/changelog/2026-06-23-removed-select-button-from-library-hover-a-clip-to-select-it.md +6 -0
  111. package/corpus/templates/clips/changelog/2026-06-23-screen-camera-recordings-now-keep-the-presenter-s-face-in-th.md +6 -0
  112. package/corpus/templates/clips/changelog/2026-06-23-shared-video-links-now-play-for-anyone-without-signing-in.md +6 -0
  113. package/corpus/templates/clips/changelog/2026-06-23-skip-or-cancel-the-recording-countdown-with-buttons-beside-t.md +6 -0
  114. package/corpus/templates/clips/changelog/2026-06-23-the-desktop-camera-bubble-now-looks-sharp-the-instant-it-ope.md +6 -0
  115. package/corpus/templates/clips/changelog/2026-06-23-the-desktop-recording-widget-now-grows-downward-only-when-yo.md +6 -0
  116. package/corpus/templates/clips/changelog/2026-06-23-the-recording-start-sound-is-now-a-softer-more-modern-chime.md +6 -0
  117. package/corpus/templates/clips/chrome-extension/public/manifest.json +17 -8
  118. package/corpus/templates/clips/chrome-extension/src/background.ts +769 -180
  119. package/corpus/templates/clips/chrome-extension/src/content-script.ts +177 -0
  120. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +466 -329
  121. package/corpus/templates/clips/chrome-extension/src/overlay.css +377 -0
  122. package/corpus/templates/clips/chrome-extension/src/overlay.html +12 -0
  123. package/corpus/templates/clips/chrome-extension/src/overlay.ts +330 -0
  124. package/corpus/templates/clips/chrome-extension/src/permission.html +227 -0
  125. package/corpus/templates/clips/chrome-extension/src/permission.ts +105 -0
  126. package/corpus/templates/clips/chrome-extension/src/popup.html +3 -0
  127. package/corpus/templates/clips/chrome-extension/src/popup.ts +50 -2
  128. package/corpus/templates/clips/chrome-extension/src/styles.css +50 -0
  129. package/corpus/templates/clips/chrome-extension/vite.config.ts +3 -0
  130. package/corpus/templates/clips/desktop/src/lib/audio-cue.ts +41 -24
  131. package/corpus/templates/clips/desktop/src/lib/bubble-webrtc.ts +119 -2
  132. package/corpus/templates/clips/desktop/src/lib/recorder.ts +20 -0
  133. package/corpus/templates/clips/desktop/src/main.tsx +3 -0
  134. package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +21 -2
  135. package/corpus/templates/clips/desktop/src/overlays/region-record-border.tsx +76 -0
  136. package/corpus/templates/clips/desktop/src/styles.css +119 -2
  137. package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +2 -1
  138. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +140 -1
  139. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -0
  140. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +4 -1
  141. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +85 -6
  142. package/corpus/templates/clips/shared/share-attribution.ts +79 -0
  143. package/corpus/templates/content/AGENTS.md +20 -3
  144. package/corpus/templates/content/CHANGELOG.md +10 -0
  145. package/corpus/templates/content/actions/_database-utils.ts +15 -0
  146. package/corpus/templates/content/actions/_property-utils.ts +371 -4
  147. package/corpus/templates/content/actions/configure-document-property.ts +35 -1
  148. package/corpus/templates/content/actions/create-content-database.ts +5 -1
  149. package/corpus/templates/content/actions/delete-document-property.ts +52 -2
  150. package/corpus/templates/content/actions/duplicate-document-property.ts +34 -17
  151. package/corpus/templates/content/actions/reorder-document-property.ts +79 -0
  152. package/corpus/templates/content/actions/set-document-property.ts +39 -0
  153. package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +806 -0
  154. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +226 -68
  155. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +59 -30
  156. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +25 -1
  157. package/corpus/templates/content/app/components/editor/blockFieldSaveController.ts +180 -0
  158. package/corpus/templates/content/app/components/editor/blockFieldSaveRegistry.ts +179 -0
  159. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +244 -0
  160. package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +132 -0
  161. package/corpus/templates/content/app/global.css +9 -0
  162. package/corpus/templates/content/app/hooks/use-document-properties.ts +21 -0
  163. package/corpus/templates/content/app/root.tsx +7 -1
  164. package/corpus/templates/content/server/db/schema.ts +30 -0
  165. package/corpus/templates/content/server/plugins/db.ts +87 -0
  166. package/corpus/templates/content/shared/api.ts +7 -0
  167. package/corpus/templates/content/shared/properties.ts +109 -0
  168. package/corpus/templates/design/CHANGELOG.md +10 -0
  169. package/corpus/templates/design/actions/generate-design.ts +4 -0
  170. package/corpus/templates/design/app/root.tsx +7 -1
  171. package/corpus/templates/design/app/routes/settings.tsx +10 -2
  172. package/corpus/templates/dispatch/CHANGELOG.md +10 -0
  173. package/corpus/templates/dispatch/app/root.tsx +7 -1
  174. package/corpus/templates/forms/.agents/skills/form-responses/SKILL.md +10 -0
  175. package/corpus/templates/forms/CHANGELOG.md +10 -0
  176. package/corpus/templates/forms/actions/export-responses.ts +6 -0
  177. package/corpus/templates/forms/actions/list-responses.ts +2 -0
  178. package/corpus/templates/forms/actions/response-insights.ts +2 -0
  179. package/corpus/templates/forms/app/pages/ResponsesPage.tsx +138 -1
  180. package/corpus/templates/forms/app/root.tsx +7 -1
  181. package/corpus/templates/forms/changelog/2026-06-23-response-tables-now-show-the-page-each-submission-came-from-.md +6 -0
  182. package/corpus/templates/forms/changelog/2026-06-23-response-tables-now-show-whether-feedback-came-from-the-web-.md +6 -0
  183. package/corpus/templates/forms/server/db/schema.ts +7 -0
  184. package/corpus/templates/forms/server/handlers/submissions.ts +19 -2
  185. package/corpus/templates/forms/server/lib/integrations.ts +76 -2
  186. package/corpus/templates/forms/server/plugins/db.ts +20 -0
  187. package/corpus/templates/forms/shared/types.ts +12 -0
  188. package/corpus/templates/macros/CHANGELOG.md +10 -0
  189. package/corpus/templates/macros/app/root.tsx +7 -1
  190. package/corpus/templates/mail/CHANGELOG.md +10 -0
  191. package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +3 -0
  192. package/corpus/templates/mail/app/pages/SettingsPage.tsx +27 -0
  193. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +31 -17
  194. package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -4
  195. package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +34 -24
  196. package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -4
  197. package/corpus/templates/plan/AGENTS.md +4 -0
  198. package/corpus/templates/plan/CHANGELOG.md +10 -0
  199. package/corpus/templates/plan/actions/view-screen.ts +5 -0
  200. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +98 -1
  201. package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +3 -1
  202. package/corpus/templates/plan/app/pages/PlansPage.tsx +107 -7
  203. package/corpus/templates/plan/app/root.tsx +7 -1
  204. package/corpus/templates/plan/changelog/2026-06-23-large-diagrams-in-a-plan-now-scroll-within-their-block-inste.md +6 -0
  205. package/corpus/templates/plan/shared/share-attribution.ts +72 -0
  206. package/corpus/templates/slides/CHANGELOG.md +10 -0
  207. package/corpus/templates/slides/app/root.tsx +7 -1
  208. package/corpus/templates/videos/CHANGELOG.md +4 -151
  209. package/corpus/templates/videos/app/root.tsx +7 -1
  210. package/dist/action.js +87 -0
  211. package/dist/action.js.map +1 -1
  212. package/dist/agent/engine/builder-engine.d.ts +1 -1
  213. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  214. package/dist/agent/model-config.d.ts +2 -2
  215. package/dist/agent/model-config.d.ts.map +1 -1
  216. package/dist/agent/model-config.js +0 -2
  217. package/dist/agent/model-config.js.map +1 -1
  218. package/dist/agent/run-loop-with-resume.d.ts +13 -0
  219. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  220. package/dist/agent/run-loop-with-resume.js +44 -0
  221. package/dist/agent/run-loop-with-resume.js.map +1 -1
  222. package/dist/changelog/parse.d.ts +70 -0
  223. package/dist/changelog/parse.d.ts.map +1 -0
  224. package/dist/changelog/parse.js +200 -0
  225. package/dist/changelog/parse.js.map +1 -0
  226. package/dist/cli/changelog.d.ts +4 -0
  227. package/dist/cli/changelog.d.ts.map +1 -0
  228. package/dist/cli/changelog.js +166 -0
  229. package/dist/cli/changelog.js.map +1 -0
  230. package/dist/cli/index.js +18 -0
  231. package/dist/cli/index.js.map +1 -1
  232. package/dist/cli/recap.d.ts.map +1 -1
  233. package/dist/cli/recap.js +22 -1
  234. package/dist/cli/recap.js.map +1 -1
  235. package/dist/cli/skills.d.ts +3 -3
  236. package/dist/cli/skills.d.ts.map +1 -1
  237. package/dist/cli/skills.js +74 -47
  238. package/dist/cli/skills.js.map +1 -1
  239. package/dist/client/AgentPanel.d.ts.map +1 -1
  240. package/dist/client/AgentPanel.js +5 -3
  241. package/dist/client/AgentPanel.js.map +1 -1
  242. package/dist/client/AssistantChat.d.ts +6 -1
  243. package/dist/client/AssistantChat.d.ts.map +1 -1
  244. package/dist/client/AssistantChat.js +2 -2
  245. package/dist/client/AssistantChat.js.map +1 -1
  246. package/dist/client/CommandMenu.d.ts +17 -1
  247. package/dist/client/CommandMenu.d.ts.map +1 -1
  248. package/dist/client/CommandMenu.js +49 -13
  249. package/dist/client/CommandMenu.js.map +1 -1
  250. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  251. package/dist/client/agent-chat-adapter.js +43 -6
  252. package/dist/client/agent-chat-adapter.js.map +1 -1
  253. package/dist/client/analytics.d.ts +18 -0
  254. package/dist/client/analytics.d.ts.map +1 -1
  255. package/dist/client/analytics.js +177 -0
  256. package/dist/client/analytics.js.map +1 -1
  257. package/dist/client/changelog/Changelog.d.ts +47 -0
  258. package/dist/client/changelog/Changelog.d.ts.map +1 -0
  259. package/dist/client/changelog/Changelog.js +138 -0
  260. package/dist/client/changelog/Changelog.js.map +1 -0
  261. package/dist/client/client-surface.d.ts +17 -0
  262. package/dist/client/client-surface.d.ts.map +1 -0
  263. package/dist/client/client-surface.js +24 -0
  264. package/dist/client/client-surface.js.map +1 -0
  265. package/dist/client/composer/TiptapComposer.d.ts +26 -1
  266. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  267. package/dist/client/composer/TiptapComposer.js +27 -5
  268. package/dist/client/composer/TiptapComposer.js.map +1 -1
  269. package/dist/client/feedback-context.d.ts +3 -0
  270. package/dist/client/feedback-context.d.ts.map +1 -1
  271. package/dist/client/feedback-context.js +2 -0
  272. package/dist/client/feedback-context.js.map +1 -1
  273. package/dist/client/index.d.ts +3 -1
  274. package/dist/client/index.d.ts.map +1 -1
  275. package/dist/client/index.js +3 -1
  276. package/dist/client/index.js.map +1 -1
  277. package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
  278. package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  279. package/dist/client/mcp-apps/McpAppRenderer.js +42 -1
  280. package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
  281. package/dist/client/settings/useBuilderStatus.d.ts +2 -0
  282. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  283. package/dist/client/settings/useBuilderStatus.js +58 -10
  284. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  285. package/dist/client/sse-event-processor.d.ts.map +1 -1
  286. package/dist/client/sse-event-processor.js +7 -1
  287. package/dist/client/sse-event-processor.js.map +1 -1
  288. package/dist/mcp/build-server.d.ts +21 -0
  289. package/dist/mcp/build-server.d.ts.map +1 -1
  290. package/dist/mcp/build-server.js +43 -1
  291. package/dist/mcp/build-server.js.map +1 -1
  292. package/dist/mcp/embed-app.d.ts.map +1 -1
  293. package/dist/mcp/embed-app.js +197 -7
  294. package/dist/mcp/embed-app.js.map +1 -1
  295. package/dist/mcp/oauth-route.d.ts.map +1 -1
  296. package/dist/mcp/oauth-route.js +95 -12
  297. package/dist/mcp/oauth-route.js.map +1 -1
  298. package/dist/server/attribution.d.ts +83 -0
  299. package/dist/server/attribution.d.ts.map +1 -0
  300. package/dist/server/attribution.js +178 -0
  301. package/dist/server/attribution.js.map +1 -0
  302. package/dist/server/auth.d.ts.map +1 -1
  303. package/dist/server/auth.js +7 -0
  304. package/dist/server/auth.js.map +1 -1
  305. package/dist/server/better-auth-instance.d.ts +9 -1
  306. package/dist/server/better-auth-instance.d.ts.map +1 -1
  307. package/dist/server/better-auth-instance.js +32 -2
  308. package/dist/server/better-auth-instance.js.map +1 -1
  309. package/dist/server/builder-browser.d.ts +4 -0
  310. package/dist/server/builder-browser.d.ts.map +1 -1
  311. package/dist/server/builder-browser.js +20 -1
  312. package/dist/server/builder-browser.js.map +1 -1
  313. package/dist/server/core-routes-plugin.d.ts +25 -0
  314. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  315. package/dist/server/core-routes-plugin.js +101 -0
  316. package/dist/server/core-routes-plugin.js.map +1 -1
  317. package/dist/server/google-oauth.d.ts.map +1 -1
  318. package/dist/server/google-oauth.js +16 -2
  319. package/dist/server/google-oauth.js.map +1 -1
  320. package/dist/server/onboarding-html.d.ts.map +1 -1
  321. package/dist/server/onboarding-html.js +35 -0
  322. package/dist/server/onboarding-html.js.map +1 -1
  323. package/dist/shared/mcp-embed-headers.js +1 -1
  324. package/dist/shared/mcp-embed-headers.js.map +1 -1
  325. package/dist/styles/agent-conversation.css +21 -0
  326. package/dist/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  327. package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
  328. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  329. package/docs/design/durable-agent-runs.md +217 -0
  330. package/package.json +1 -1
  331. package/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  332. package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
  333. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  334. package/corpus/templates/analytics/app/pages/About.tsx +0 -108
  335. package/corpus/templates/analytics/app/routes/about.tsx +0 -9
  336. /package/corpus/templates/analytics/app/routes/{dashboards.tsx → dashboards._index.tsx} +0 -0
@@ -0,0 +1,211 @@
1
+ /**
2
+ * First-touch referral attribution — server side.
3
+ *
4
+ * The browser captures an anonymous visitor's *first* landing context (referral
5
+ * source, UTM params, referring host, landing path) and persists it across the
6
+ * signup boundary in a first-party cookie named `an_ft` (see
7
+ * `client/analytics.ts`). On signup, the auth hook reads that cookie off the
8
+ * request and enriches the canonical server-side `signup` event so we can
9
+ * measure where new users came from and how apps spread (virality).
10
+ *
11
+ * This module is intentionally pure and dependency-free so it is trivially
12
+ * unit-testable and can never throw into the signup path. The single hard rule:
13
+ * parsing untrusted cookie input must NEVER throw — every accessor is defensive.
14
+ */
15
+
16
+ /**
17
+ * The decoded first-touch attribution object. Mirrors the compact JSON the
18
+ * client writes into the `an_ft` cookie / `an_attribution` localStorage key.
19
+ * Every field is optional — the client omits empty fields to keep the cookie
20
+ * small, and a malformed/absent cookie yields `null`.
21
+ */
22
+ export interface FirstTouchAttribution {
23
+ /** Referral source bucket, e.g. "clip_share", "plan_share". */
24
+ ref?: string;
25
+ /** Referrer's stable user id (the clip/plan owner who shared the link). */
26
+ via?: string;
27
+ utm_source?: string;
28
+ utm_medium?: string;
29
+ utm_campaign?: string;
30
+ utm_content?: string;
31
+ utm_term?: string;
32
+ /** `window.location.pathname` of the first page the visitor landed on. */
33
+ landing_path?: string;
34
+ /** Host of `document.referrer` (scrubbed; host only, never a full URL). */
35
+ landing_referrer?: string;
36
+ /** ISO timestamp of when the visitor first landed. */
37
+ landed_at?: string;
38
+ }
39
+
40
+ /** Cookie name written by the client (non-HttpOnly; non-sensitive). */
41
+ export const FIRST_TOUCH_COOKIE_NAME = "an_ft";
42
+
43
+ const STRING_FIELDS: Array<keyof FirstTouchAttribution> = [
44
+ "ref",
45
+ "via",
46
+ "utm_source",
47
+ "utm_medium",
48
+ "utm_campaign",
49
+ "utm_content",
50
+ "utm_term",
51
+ "landing_path",
52
+ "landing_referrer",
53
+ "landed_at",
54
+ ];
55
+
56
+ /**
57
+ * Parse a raw `Cookie:` header into a flat name→value map. Tolerates missing
58
+ * input, extra whitespace, `=` inside values, and malformed pairs. Never throws.
59
+ */
60
+ export function parseCookieHeader(
61
+ cookieHeader: string | null | undefined,
62
+ ): Record<string, string> {
63
+ const out: Record<string, string> = {};
64
+ if (!cookieHeader || typeof cookieHeader !== "string") return out;
65
+ for (const part of cookieHeader.split(";")) {
66
+ const eq = part.indexOf("=");
67
+ if (eq === -1) continue;
68
+ const name = part.slice(0, eq).trim();
69
+ if (!name) continue;
70
+ const rawValue = part.slice(eq + 1).trim();
71
+ // First write wins so a duplicate cookie name can't clobber the first.
72
+ if (name in out) continue;
73
+ out[name] = rawValue;
74
+ }
75
+ return out;
76
+ }
77
+
78
+ /**
79
+ * Decode a single cookie value into a `FirstTouchAttribution`. The value is the
80
+ * URL-encoded compact JSON written by the client. Returns `null` for empty,
81
+ * malformed, or non-object input. Only known string fields are copied through,
82
+ * each clamped to a sane max length as a defense against an oversized cookie.
83
+ */
84
+ export function decodeFirstTouchValue(
85
+ value: string | null | undefined,
86
+ ): FirstTouchAttribution | null {
87
+ if (!value || typeof value !== "string") return null;
88
+ let decoded = value;
89
+ try {
90
+ decoded = decodeURIComponent(value);
91
+ } catch {
92
+ // Not valid percent-encoding — fall back to the raw value and let the JSON
93
+ // parse below decide whether it's usable.
94
+ decoded = value;
95
+ }
96
+ let parsed: unknown;
97
+ try {
98
+ parsed = JSON.parse(decoded);
99
+ } catch {
100
+ return null;
101
+ }
102
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
103
+ return null;
104
+ }
105
+ const source = parsed as Record<string, unknown>;
106
+ const result: FirstTouchAttribution = {};
107
+ let any = false;
108
+ for (const field of STRING_FIELDS) {
109
+ const raw = source[field];
110
+ if (typeof raw === "string" && raw.length > 0) {
111
+ result[field] = raw.slice(0, 120);
112
+ any = true;
113
+ }
114
+ }
115
+ return any ? result : null;
116
+ }
117
+
118
+ /**
119
+ * Read the `an_ft` first-touch attribution out of a raw `Cookie:` header.
120
+ * Returns `null` when the cookie is absent or unparseable. Never throws.
121
+ */
122
+ export function readFirstTouchAttribution(
123
+ cookieHeader: string | null | undefined,
124
+ ): FirstTouchAttribution | null {
125
+ try {
126
+ const cookies = parseCookieHeader(cookieHeader);
127
+ return decodeFirstTouchValue(cookies[FIRST_TOUCH_COOKIE_NAME]);
128
+ } catch {
129
+ return null;
130
+ }
131
+ }
132
+
133
+ function isExternalReferrerHost(host: string | undefined): boolean {
134
+ const trimmed = host?.trim();
135
+ return !!trimmed && trimmed.length > 0;
136
+ }
137
+
138
+ /**
139
+ * Derive the `referral_source` bucket from first-touch attribution per the
140
+ * contract:
141
+ * 1. explicit `ref` wins;
142
+ * 2. landing path under `/share/` => "clip_share";
143
+ * 3. landing path that looks like a public plan page
144
+ * (contains `/p/`, `/plan/`, or `/share-plan/`) => "plan_share";
145
+ * 4. a non-empty external referring host => "external";
146
+ * 5. otherwise => "direct".
147
+ */
148
+ export function deriveReferralSource(ft: FirstTouchAttribution | null): string {
149
+ if (ft?.ref && ft.ref.trim()) return ft.ref.trim();
150
+ const path = ft?.landing_path ?? "";
151
+ if (path.startsWith("/share/")) return "clip_share";
152
+ if (
153
+ path.includes("/p/") ||
154
+ path.includes("/plan/") ||
155
+ path.includes("/share-plan/")
156
+ ) {
157
+ return "plan_share";
158
+ }
159
+ if (isExternalReferrerHost(ft?.landing_referrer)) return "external";
160
+ return "direct";
161
+ }
162
+
163
+ /**
164
+ * Compute the snake_case signup-event properties from first-touch attribution.
165
+ * Returns a clean object with `undefined` values omitted, ready to merge into
166
+ * the `signup` track call. Always sets `referral_source` (defaults to "direct").
167
+ *
168
+ * Pure and total — given any (or no) input it returns a well-formed object and
169
+ * never throws.
170
+ */
171
+ export function deriveSignupAttribution(
172
+ ft: FirstTouchAttribution | null,
173
+ ): Record<string, string> {
174
+ const out: Record<string, string> = {
175
+ referral_source: deriveReferralSource(ft),
176
+ };
177
+ if (!ft) return out;
178
+
179
+ const setIf = (key: string, value: string | undefined) => {
180
+ const trimmed = value?.trim();
181
+ if (trimmed) out[key] = trimmed;
182
+ };
183
+
184
+ setIf("referrer_user", ft.via);
185
+ setIf("referral_medium", ft.utm_medium);
186
+ setIf("referral_campaign", ft.utm_campaign);
187
+ setIf("utm_source", ft.utm_source);
188
+ setIf("utm_medium", ft.utm_medium);
189
+ setIf("utm_campaign", ft.utm_campaign);
190
+ setIf("utm_content", ft.utm_content);
191
+ setIf("utm_term", ft.utm_term);
192
+ setIf("first_touch_path", ft.landing_path);
193
+ setIf("landing_referrer", ft.landing_referrer);
194
+
195
+ return out;
196
+ }
197
+
198
+ /**
199
+ * Convenience: read the cookie header and derive signup attribution in one
200
+ * call. Never throws; falls back to `{ referral_source: "direct" }` on any
201
+ * error.
202
+ */
203
+ export function signupAttributionFromCookieHeader(
204
+ cookieHeader: string | null | undefined,
205
+ ): Record<string, string> {
206
+ try {
207
+ return deriveSignupAttribution(readFirstTouchAttribution(cookieHeader));
208
+ } catch {
209
+ return { referral_source: "direct" };
210
+ }
211
+ }
@@ -1796,6 +1796,12 @@ function createAuthGuardFn(): (
1796
1796
  // route tree, no per-user data.
1797
1797
  if (p === "/__manifest") return;
1798
1798
  if (p === "/_agent-native/speculation-rules.json") return;
1799
+ // Liveness probe: always public so uptime monitors and the keep-warm cron
1800
+ // can reach the DB-warmup route without a session. It exposes no per-user
1801
+ // data (just ok/db/ms) and runs a trivial `SELECT 1`. Without this bypass
1802
+ // the gate below 401s anonymous /_agent-native/* requests before any DB
1803
+ // query, so the database would never get warmed.
1804
+ if (p === "/_agent-native/health") return;
1799
1805
  if (isPublicPath(normalizedUrl, publicPaths)) return;
1800
1806
  if (shouldBypassAuthForBuilderConnect(event, p)) return;
1801
1807
  if (isPublicWorkspacePageRequest(event, p, config)) {
@@ -23,6 +23,7 @@ import { acceptPendingInvitationsForEmail } from "../org/accept-pending.js";
23
23
  import { autoJoinDomainMatchingOrgs } from "../org/auto-join-domain.js";
24
24
  import { saveOAuthTokens } from "../oauth-tokens/store.js";
25
25
  import { flushTracking, identify, track } from "../tracking/index.js";
26
+ import { signupAttributionFromCookieHeader } from "./attribution.js";
26
27
  import { TEMPLATES } from "../cli/templates-meta.js";
27
28
  import { resolveAuthCookieNamespace } from "./cookie-namespace.js";
28
29
  import { getWorkspaceA2ADerivedSecret } from "./derived-secret.js";
@@ -72,23 +73,41 @@ export async function trackSignupEvent({
72
73
  authUserId,
73
74
  email,
74
75
  name,
76
+ attribution,
75
77
  }: {
76
78
  authProvider: string;
77
79
  authUserId?: string;
78
80
  email: string;
79
81
  name?: string | null;
82
+ /**
83
+ * First-touch referral attribution derived from the visitor's `an_ft`
84
+ * cookie (see `server/attribution.ts`). Snake_case keys such as
85
+ * `referral_source`, `referrer_user`, and the UTM passthrough are merged
86
+ * into the `signup` event so we can measure where new users came from.
87
+ * `undefined` values are dropped; a missing object is a clean no-op.
88
+ */
89
+ attribution?: Record<string, string | undefined>;
80
90
  }): Promise<void> {
81
91
  identify(email, {
82
92
  email,
83
93
  name: name ?? undefined,
84
94
  authUserId,
85
95
  });
96
+ const cleanAttribution: Record<string, string> = {};
97
+ if (attribution) {
98
+ for (const [key, value] of Object.entries(attribution)) {
99
+ if (typeof value === "string" && value.length > 0) {
100
+ cleanAttribution[key] = value;
101
+ }
102
+ }
103
+ }
86
104
  track(
87
105
  "signup",
88
106
  {
89
107
  ...resolveSignupTrackingProperties(),
90
108
  auth_provider: authProvider,
91
109
  ...(authUserId ? { auth_user_id: authUserId } : {}),
110
+ ...cleanAttribution,
92
111
  },
93
112
  { userId: email },
94
113
  );
@@ -929,22 +948,47 @@ async function createBetterAuthInstance(
929
948
  databaseHooks: {
930
949
  user: {
931
950
  create: {
932
- after: async (user: {
933
- id?: string;
934
- email?: string;
935
- name?: string | null;
936
- }) => {
951
+ after: async (
952
+ user: {
953
+ id?: string;
954
+ email?: string;
955
+ name?: string | null;
956
+ },
957
+ // Better Auth (1.6.x) passes the endpoint context as the 2nd arg.
958
+ // It carries the originating request's headers (and on OAuth
959
+ // signups the callback request's headers), which is where the
960
+ // browser's `an_ft` first-touch cookie rides in.
961
+ context?: {
962
+ headers?: Headers | null;
963
+ request?: { headers?: Headers | null } | null;
964
+ } | null,
965
+ ) => {
937
966
  // When a newly-created user's email has pending org invitations
938
967
  // (common when someone is invited *before* they've signed up),
939
968
  // auto-accept them so the user lands in the org on their very
940
969
  // first page load instead of a blank-slate workspace.
941
970
  const email = user?.email;
942
971
  if (!email) return;
972
+ // Derive first-touch referral attribution from the request's
973
+ // cookie header. Never let attribution parsing throw or block
974
+ // signup — on any error fall back to `direct`.
975
+ let attribution: Record<string, string> | undefined;
976
+ try {
977
+ const cookieHeader =
978
+ context?.headers?.get("cookie") ??
979
+ context?.request?.headers?.get("cookie") ??
980
+ null;
981
+ attribution = signupAttributionFromCookieHeader(cookieHeader);
982
+ } catch (err) {
983
+ console.error("[auth] failed to derive signup attribution", err);
984
+ attribution = undefined;
985
+ }
943
986
  await trackSignupEvent({
944
987
  authProvider: "better-auth",
945
988
  authUserId: user.id,
946
989
  email,
947
990
  name: user.name,
991
+ attribution,
948
992
  });
949
993
  try {
950
994
  await acceptPendingInvitationsForEmail(email);
@@ -1,7 +1,10 @@
1
1
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
2
2
  import type { H3Event } from "h3";
3
3
  import { getHeader } from "h3";
4
- import { getAuthSecret } from "./better-auth-instance.js";
4
+ import {
5
+ getAuthSecret,
6
+ resolveSignupTrackingIdentity,
7
+ } from "./better-auth-instance.js";
5
8
  import { getAppBasePath, getOrigin } from "./google-oauth.js";
6
9
 
7
10
  const DEFAULT_BUILDER_APP_HOST = "https://builder.io";
@@ -43,6 +46,8 @@ export const BUILDER_SIGNUP_SOURCE_PARAM = "signupSource";
43
46
  export const BUILDER_AGENT_NATIVE_FLOW_PARAM = "agentNativeFlow";
44
47
  export const BUILDER_AGENT_NATIVE_CONNECT_SOURCE_PARAM =
45
48
  "agentNativeConnectSource";
49
+ export const BUILDER_AGENT_NATIVE_APP_PARAM = "agentNativeApp";
50
+ export const BUILDER_AGENT_NATIVE_TEMPLATE_PARAM = "agentNativeTemplate";
46
51
 
47
52
  const BUILDER_STATE_TTL_MS = 10 * 60 * 1000;
48
53
  const BUILDER_SIGNUP_SOURCE = "agent-native";
@@ -51,6 +56,8 @@ export interface BuilderConnectTrackingParams {
51
56
  signupSource?: string;
52
57
  agentNativeFlow?: string;
53
58
  agentNativeConnectSource?: string;
59
+ agentNativeApp?: string;
60
+ agentNativeTemplate?: string;
54
61
  }
55
62
 
56
63
  function cleanTrackingParam(value: unknown): string | undefined {
@@ -72,6 +79,12 @@ export function getBuilderConnectTrackingParams(
72
79
  agentNativeConnectSource: cleanTrackingParam(
73
80
  params.get(BUILDER_AGENT_NATIVE_CONNECT_SOURCE_PARAM),
74
81
  ),
82
+ agentNativeApp: cleanTrackingParam(
83
+ params.get(BUILDER_AGENT_NATIVE_APP_PARAM),
84
+ ),
85
+ agentNativeTemplate: cleanTrackingParam(
86
+ params.get(BUILDER_AGENT_NATIVE_TEMPLATE_PARAM),
87
+ ),
75
88
  };
76
89
  }
77
90
 
@@ -86,6 +99,12 @@ export function builderConnectTrackingProperties(
86
99
  if (tracking.agentNativeConnectSource) {
87
100
  properties.agent_native_connect_source = tracking.agentNativeConnectSource;
88
101
  }
102
+ if (tracking.agentNativeApp) {
103
+ properties.agent_native_app = tracking.agentNativeApp;
104
+ }
105
+ if (tracking.agentNativeTemplate) {
106
+ properties.agent_native_template = tracking.agentNativeTemplate;
107
+ }
89
108
  return properties;
90
109
  }
91
110
 
@@ -101,6 +120,10 @@ function applyBuilderConnectTrackingParams(
101
120
  if (flow) params.set(BUILDER_AGENT_NATIVE_FLOW_PARAM, flow);
102
121
  const source = cleanTrackingParam(tracking.agentNativeConnectSource);
103
122
  if (source) params.set(BUILDER_AGENT_NATIVE_CONNECT_SOURCE_PARAM, source);
123
+ const app = cleanTrackingParam(tracking.agentNativeApp);
124
+ if (app) params.set(BUILDER_AGENT_NATIVE_APP_PARAM, app);
125
+ const template = cleanTrackingParam(tracking.agentNativeTemplate);
126
+ if (template) params.set(BUILDER_AGENT_NATIVE_TEMPLATE_PARAM, template);
104
127
  }
105
128
 
106
129
  export interface BuilderBrowserStatus {
@@ -524,8 +547,11 @@ export function buildBuilderCliAuthUrl(
524
547
  ) {
525
548
  callbackUrl.searchParams.set(BUILDER_OPENER_PARAM, requestedPreviewOrigin);
526
549
  }
550
+ const identity = resolveSignupTrackingIdentity();
527
551
  const tracking = {
528
552
  signupSource: BUILDER_SIGNUP_SOURCE,
553
+ agentNativeApp: identity.app,
554
+ agentNativeTemplate: identity.template,
529
555
  ...options.tracking,
530
556
  };
531
557
  applyBuilderConnectTrackingParams(callbackUrl.searchParams, tracking);
@@ -32,6 +32,7 @@ import {
32
32
  MCP_EMBED_CORS_ALLOW_HEADERS,
33
33
  shouldAllowMcpEmbedCredentials,
34
34
  } from "../shared/mcp-embed-headers.js";
35
+ import { captureError } from "./capture-error.js";
35
36
  import { handleMcpConnect } from "../mcp/connect-route.js";
36
37
  import {
37
38
  handleMcpOAuth,
@@ -145,6 +146,7 @@ import { isEnvVarWriteAllowed } from "./env-var-writes.js";
145
146
  import { llmConnectionTrackingProperties } from "../shared/llm-connection.js";
146
147
  import { mountBrowserSessionRoutes } from "../browser-sessions/routes.js";
147
148
  import { mountDbAdminRoutes } from "../db-admin/routes.js";
149
+ import { getDbExec } from "../db/client.js";
148
150
  import {
149
151
  DEFAULT_SSR_CACHE_HEADERS,
150
152
  EMPTY_SPECULATION_RULES,
@@ -158,6 +160,40 @@ import {
158
160
  export const FRAMEWORK_ROUTE_PREFIX = "/_agent-native";
159
161
  export const FRAMEWORK_EVENTS_ROUTE = `${FRAMEWORK_ROUTE_PREFIX}/events`;
160
162
  export const LEGACY_FRAMEWORK_EVENTS_ROUTE = `${FRAMEWORK_ROUTE_PREFIX}/poll-events`;
163
+
164
+ /** Result of the `/_agent-native/health` liveness + DB-warmup probe. */
165
+ export interface DbHealthProbeResult {
166
+ /** The serverless function is live and served the request. */
167
+ ok: true;
168
+ /** A trivial `SELECT 1` reached the database (false = no DB or unreachable). */
169
+ db: boolean;
170
+ /** Round-trip time of the probe in milliseconds. */
171
+ ms: number;
172
+ }
173
+
174
+ /**
175
+ * Run a trivial `SELECT 1` to confirm the database is reachable and, as a side
176
+ * effect, keep a scale-to-zero serverless database (e.g. Neon) warm. Touching
177
+ * the DB on a schedule prevents the multi-second cold-start that otherwise
178
+ * stalls the next real user request.
179
+ *
180
+ * Always resolves: an app with no database (or a momentarily unreachable one)
181
+ * is still live, so the probe reports `db: false` rather than throwing. The
182
+ * `exec` parameter is injectable purely for tests.
183
+ */
184
+ export async function runDbHealthProbe(
185
+ exec: () => { execute: (sql: string) => Promise<unknown> } = getDbExec,
186
+ ): Promise<DbHealthProbeResult> {
187
+ const startedAt = Date.now();
188
+ let db = false;
189
+ try {
190
+ await exec().execute("SELECT 1");
191
+ db = true;
192
+ } catch {
193
+ // Live even when the DB is unreachable or the app has no database.
194
+ }
195
+ return { ok: true, db, ms: Date.now() - startedAt };
196
+ }
161
197
  const DEFAULT_BUILDER_WAITLIST_FORM_ID = "DYTHuM0jlV";
162
198
  const DEFAULT_BUILDER_WAITLIST_FORMS_ORIGIN = "https://forms.agent-native.com";
163
199
  const BUILDER_WAITLIST_FORM_SOURCE = "connect_builder_card";
@@ -550,6 +586,8 @@ export interface CoreRoutesPluginOptions {
550
586
  disableSSE?: boolean;
551
587
  /** Disable the /_agent-native/ping health check. */
552
588
  disablePing?: boolean;
589
+ /** Disable the /_agent-native/health DB liveness + warmup probe. */
590
+ disableHealth?: boolean;
553
591
  /** Disable the /_agent-native/application-state routes. */
554
592
  disableAppState?: boolean;
555
593
  /** Disable the /_agent-native/open deep-link route. */
@@ -594,6 +632,7 @@ export interface CoreRoutesPluginOptions {
594
632
  * GET /_agent-native/poll — polling endpoint for change detection
595
633
  * GET /_agent-native/events (or custom) — SSE endpoint for real-time sync
596
634
  * GET /_agent-native/ping — health check
635
+ * GET /_agent-native/health — DB liveness probe + scale-to-zero warmup
597
636
  * GET /_agent-native/env-status — env key configuration status (when envKeys provided)
598
637
  * POST /_agent-native/env-vars — save env vars to .env (when envKeys provided)
599
638
  * GET /_agent-native/application-state/:key — read application state
@@ -925,6 +964,20 @@ export function createCoreRoutesPlugin(
925
964
  );
926
965
  }
927
966
 
967
+ // Health + DB warmup — liveness probe that touches the database so
968
+ // uptime monitors and the keep-warm cron prevent a scale-to-zero
969
+ // serverless DB (e.g. Neon) from cold-starting on the next real
970
+ // request. Public, side-effect free, and never cached.
971
+ if (!options.disableHealth) {
972
+ getH3App(nitroApp).use(
973
+ `${P}/health`,
974
+ defineEventHandler(async (event) => {
975
+ setResponseHeader(event, "cache-control", "no-store");
976
+ return runDbHealthProbe();
977
+ }),
978
+ );
979
+ }
980
+
928
981
  getH3App(nitroApp).use(
929
982
  `${P}/speculation-rules.json`,
930
983
  defineEventHandler((event) => {
@@ -1691,6 +1744,12 @@ export function createCoreRoutesPlugin(
1691
1744
  agentNativeConnectSource:
1692
1745
  connectTracking.agentNativeConnectSource ??
1693
1746
  pending.tracking.agentNativeConnectSource,
1747
+ agentNativeApp:
1748
+ connectTracking.agentNativeApp ??
1749
+ pending.tracking.agentNativeApp,
1750
+ agentNativeTemplate:
1751
+ connectTracking.agentNativeTemplate ??
1752
+ pending.tracking.agentNativeTemplate,
1694
1753
  };
1695
1754
  }
1696
1755
  if (
@@ -3103,6 +3162,83 @@ export function createCoreRoutesPlugin(
3103
3162
  `${P}/embed/start`,
3104
3163
  createEmbedStartRouteHandler({ getExistingSession: getSession }),
3105
3164
  );
3165
+
3166
+ // POST /_agent-native/mcp/embed-error — telemetry sink for MCP App
3167
+ // embed shells. The shell runs in a sandboxed, opaque-origin iframe
3168
+ // (Codex, Cursor, ChatGPT, Claude) with no session cookie or CSRF
3169
+ // token, so this endpoint is intentionally unauthenticated and
3170
+ // CORS-open to the SAME sandbox origins as /embed/start. It forwards a
3171
+ // small, bounded diagnostic payload to Sentry via captureError so we
3172
+ // can see *why* an inline embed failed (handshake timeout, transplant
3173
+ // fetch status/CORS, auth, CSP) per host. Best-effort: always 204,
3174
+ // never throws, body capped, no client-trusted identity.
3175
+ getH3App(nitroApp).use(
3176
+ `${P}/mcp/embed-error`,
3177
+ defineEventHandler(async (event: H3Event) => {
3178
+ const origin = getHeader(event, "origin");
3179
+ if (origin && isMcpEmbedCorsOrigin(origin)) {
3180
+ setResponseHeader(event, "Access-Control-Allow-Origin", origin);
3181
+ setResponseHeader(event, "Vary", "Origin");
3182
+ setResponseHeader(
3183
+ event,
3184
+ "Access-Control-Allow-Methods",
3185
+ "POST,OPTIONS",
3186
+ );
3187
+ setResponseHeader(
3188
+ event,
3189
+ "Access-Control-Allow-Headers",
3190
+ MCP_EMBED_CORS_ALLOW_HEADERS,
3191
+ );
3192
+ }
3193
+ const method = getMethod(event);
3194
+ if (method === "OPTIONS") {
3195
+ setResponseStatus(event, 204);
3196
+ return "";
3197
+ }
3198
+ if (method !== "POST") {
3199
+ setResponseStatus(event, 405);
3200
+ return { error: "Method not allowed" };
3201
+ }
3202
+ const body = await readBody(event).catch(() => undefined);
3203
+ const rec =
3204
+ body && typeof body === "object" && !Array.isArray(body)
3205
+ ? (body as Record<string, unknown>)
3206
+ : {};
3207
+ const str = (value: unknown, max: number): string | undefined =>
3208
+ typeof value === "string" && value
3209
+ ? value.slice(0, max)
3210
+ : undefined;
3211
+ const message = str(rec.message, 500) ?? "MCP embed failed";
3212
+ try {
3213
+ captureError(new Error(message), {
3214
+ route: `${P}/mcp/embed-error`,
3215
+ method: "POST",
3216
+ userAgent:
3217
+ str(rec.userAgent, 300) ?? getHeader(event, "user-agent"),
3218
+ tags: {
3219
+ source: "mcp-embed-shell",
3220
+ embed_stage: str(rec.stage, 60),
3221
+ embed_render_mode: str(rec.renderMode, 40),
3222
+ embed_host: str(rec.host, 160),
3223
+ embed_bridge: str(rec.bridge, 40),
3224
+ },
3225
+ extra: {
3226
+ embedUrl: str(rec.url, 600),
3227
+ httpStatus:
3228
+ typeof rec.status === "number"
3229
+ ? rec.status
3230
+ : str(rec.status, 40),
3231
+ detail: str(rec.detail, 1200),
3232
+ origin,
3233
+ },
3234
+ });
3235
+ } catch {
3236
+ // Observability must never throw back into the request path.
3237
+ }
3238
+ setResponseStatus(event, 204);
3239
+ return "";
3240
+ }),
3241
+ );
3106
3242
  }
3107
3243
 
3108
3244
  if (!options.disableAppState) {
@@ -841,14 +841,27 @@ export function oauthCallbackResponse(
841
841
  // same-origin; Builder desktop workspace returns may point back to the
842
842
  // local loopback gateway and carry the short-lived `_session` bridge so
843
843
  // the local app can promote the newly created hosted OAuth session.
844
- setResponseStatus(event, 302);
845
- setResponseHeader(
846
- event,
847
- "Location",
848
- appendSessionToOAuthReturnUrl(opts.returnUrl, opts.sessionToken),
844
+ const location = appendSessionToOAuthReturnUrl(
845
+ opts.returnUrl,
846
+ opts.sessionToken,
849
847
  );
848
+ setResponseStatus(event, 302);
849
+ setResponseHeader(event, "Location", location);
850
850
  setResponseHeader(event, "Referrer-Policy", "no-referrer");
851
- return "";
851
+ // Return a real 302 so the browser lands on the clean return URL instead of
852
+ // lingering on the provider callback URL with its `code`/`state` query
853
+ // params. But h3 hands a non-2xx web `Response` straight back WITHOUT merging
854
+ // the `Set-Cookie` staged earlier in the callback (the framework session
855
+ // cookie), so mirror those staged cookies onto the redirect Response —
856
+ // otherwise the sign-in succeeds but the browser arrives back logged out.
857
+ const headers = new Headers({
858
+ Location: location,
859
+ "Referrer-Policy": "no-referrer",
860
+ });
861
+ for (const cookie of event.res?.headers?.getSetCookie?.() ?? []) {
862
+ headers.append("set-cookie", cookie);
863
+ }
864
+ return new Response(null, { status: 302, headers });
852
865
  }
853
866
 
854
867
  /** HTML error page for OAuth failures. The message is HTML-escaped — most