@agent-native/core 0.124.3 → 0.124.4

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 (254) hide show
  1. package/README.md +1 -1
  2. package/corpus/README.md +2 -2
  3. package/corpus/core/CHANGELOG.md +10 -0
  4. package/corpus/core/docs/content/cloneable-saas.mdx +9 -9
  5. package/corpus/core/docs/content/creating-templates.mdx +2 -2
  6. package/corpus/core/docs/content/deployment.mdx +23 -0
  7. package/corpus/core/docs/content/faq.mdx +4 -4
  8. package/corpus/core/docs/content/key-concepts.mdx +1 -1
  9. package/corpus/core/docs/content/multi-app-workspace.mdx +1 -1
  10. package/corpus/core/docs/content/syncing-template-changes.mdx +1 -1
  11. package/corpus/core/docs/content/template-analytics-developers.mdx +2 -2
  12. package/corpus/core/docs/content/template-analytics.mdx +2 -2
  13. package/corpus/core/docs/content/template-assets-developers.mdx +3 -3
  14. package/corpus/core/docs/content/template-assets.mdx +2 -2
  15. package/corpus/core/docs/content/template-brain-developers.mdx +2 -2
  16. package/corpus/core/docs/content/template-brain.mdx +1 -1
  17. package/corpus/core/docs/content/template-calendar-developers.mdx +1 -1
  18. package/corpus/core/docs/content/template-calendar.mdx +1 -1
  19. package/corpus/core/docs/content/template-chat.mdx +1 -1
  20. package/corpus/core/docs/content/template-clips-developers.mdx +4 -4
  21. package/corpus/core/docs/content/template-clips.mdx +2 -2
  22. package/corpus/core/docs/content/template-content-developers.mdx +2 -2
  23. package/corpus/core/docs/content/template-content.mdx +1 -1
  24. package/corpus/core/docs/content/template-design-brand-and-figma.mdx +1 -1
  25. package/corpus/core/docs/content/template-design-developers.mdx +2 -2
  26. package/corpus/core/docs/content/template-design.mdx +2 -2
  27. package/corpus/core/docs/content/template-dispatch-developers.mdx +5 -5
  28. package/corpus/core/docs/content/template-forms-developers.mdx +1 -1
  29. package/corpus/core/docs/content/template-forms.mdx +1 -1
  30. package/corpus/core/docs/content/template-mail-developers.mdx +2 -2
  31. package/corpus/core/docs/content/template-mail.mdx +2 -2
  32. package/corpus/core/docs/content/template-plan-developers.mdx +3 -3
  33. package/corpus/core/docs/content/template-plan.mdx +3 -3
  34. package/corpus/core/docs/content/template-slides-developers.mdx +4 -4
  35. package/corpus/core/docs/content/template-slides.mdx +1 -1
  36. package/corpus/core/docs/content/what-is-agent-native.mdx +5 -5
  37. package/corpus/core/package.json +1 -1
  38. package/corpus/core/src/a2a/auth-policy.ts +1 -1
  39. package/corpus/core/src/agent/production-agent.ts +14 -1
  40. package/corpus/core/src/agent/run-manager.ts +6 -13
  41. package/corpus/core/src/cli/index.ts +11 -1
  42. package/corpus/core/src/client/route-chunk-recovery.ts +18 -7
  43. package/corpus/core/src/db/client.ts +7 -6
  44. package/corpus/core/src/db/migrations.ts +2 -1
  45. package/corpus/core/src/deploy/build.ts +290 -4
  46. package/corpus/core/src/deploy/nitro-preset.ts +50 -0
  47. package/corpus/core/src/integrations/pending-tasks-retry-job.ts +2 -1
  48. package/corpus/core/src/scripts/call-agent.ts +4 -2
  49. package/corpus/core/src/server/agent-discovery.ts +1 -0
  50. package/corpus/core/src/server/agent-run-context.ts +13 -3
  51. package/corpus/core/src/server/request-context.ts +2 -0
  52. package/corpus/core/src/shared/cloudflare-globals.d.ts +2 -1
  53. package/corpus/core/src/shared/runtime.ts +2 -0
  54. package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +29 -0
  55. package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +6 -6
  56. package/corpus/core/src/templates/default/app/i18n/de-DE.ts +6 -6
  57. package/corpus/core/src/templates/default/app/i18n/en-US.ts +6 -6
  58. package/corpus/core/src/templates/default/app/i18n/es-ES.ts +6 -6
  59. package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +6 -6
  60. package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +6 -6
  61. package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +6 -6
  62. package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +6 -6
  63. package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +6 -6
  64. package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +6 -6
  65. package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +6 -6
  66. package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +29 -0
  67. package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -0
  68. package/corpus/core/src/vite/client.ts +20 -1
  69. package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +9 -2
  70. package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +3 -0
  71. package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +1 -1
  72. package/corpus/templates/analytics/app/global.css +10 -9
  73. package/corpus/templates/analytics/app/hooks/use-chart-tooltip-portal.ts +59 -18
  74. package/corpus/templates/analytics/app/hooks/use-dashboard-revisions.ts +14 -2
  75. package/corpus/templates/analytics/app/i18n-data.ts +102 -0
  76. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +232 -2
  77. package/corpus/templates/analytics/changelog/2026-07-27-chart-tooltips-stay-beside-the-cursor-when-they-extend-beyon.md +6 -0
  78. package/corpus/templates/analytics/changelog/2026-07-27-chat-no-longer-claims-a-data-source-is-disconnected-when-it-.md +6 -0
  79. package/corpus/templates/analytics/changelog/2026-07-27-dashboard-changes-can-be-undone-with-cmd-z-and-actions-menu.md +6 -0
  80. package/corpus/templates/analytics/changelog/2026-07-27-dashboard-redo-with-cmd-shift-z-and-actions-menu.md +6 -0
  81. package/corpus/templates/analytics/changelog/2026-07-27-dashboard-skeletons-are-subtler-and-smoother.md +6 -0
  82. package/corpus/templates/analytics/server/lib/dashboards-store.ts +61 -20
  83. package/corpus/templates/analytics/server/lib/real-data-actions.ts +1 -0
  84. package/corpus/templates/analytics/server/plugins/agent-chat.ts +7 -3
  85. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +7 -32
  86. package/corpus/templates/calendar/changelog/2026-07-27-calendar-s-sidebar-now-uses-a-cleaner-layout-with-fewer-util.md +6 -0
  87. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +14 -6
  88. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +32 -37
  89. package/corpus/templates/content/changelog/2026-07-27-content-sidebar-page-lists-now-load-with-quiet-skeletons-and.md +6 -0
  90. package/corpus/templates/design/app/components/layout/Sidebar.tsx +16 -18
  91. package/corpus/templates/design/changelog/2026-07-27-collapsed-sidebar-keeps-the-agent-native-logo.md +6 -0
  92. package/corpus/templates/dispatch/AGENTS.md +7 -2
  93. package/corpus/templates/dispatch/actions/view-screen.ts +3 -2
  94. package/corpus/templates/dispatch/changelog/2026-07-17-curated-template-remixes.md +1 -1
  95. package/corpus/templates/dispatch/changelog/2026-07-27-creating-an-app-from-a-curated-template-now-uses-clearer-mor.md +6 -0
  96. package/corpus/templates/dispatch/changelog/2026-07-27-thread-debug-accepts-request-run-ids.md +6 -0
  97. package/corpus/templates/slides/actions/create-design-system.ts +10 -2
  98. package/corpus/templates/slides/actions/import-file.ts +173 -1
  99. package/corpus/templates/slides/actions/list-slide-comments.ts +16 -1
  100. package/corpus/templates/slides/actions/update-design-system.ts +13 -2
  101. package/corpus/templates/slides/app/components/editor/AssetLibraryPanel.tsx +35 -12
  102. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +36 -0
  103. package/corpus/templates/slides/app/i18n/en-US.ts +3 -0
  104. package/corpus/templates/slides/app/lib/dynamic-import.ts +63 -0
  105. package/corpus/templates/slides/app/lib/export-pdf-client.ts +3 -2
  106. package/corpus/templates/slides/app/lib/export-pptx-client.ts +5 -2
  107. package/corpus/templates/slides/app/pages/DesignSystems.tsx +9 -1
  108. package/corpus/templates/slides/server/db/schema.ts +11 -0
  109. package/corpus/templates/slides/server/handlers/assets.ts +64 -19
  110. package/corpus/templates/slides/server/handlers/import/html-converter.ts +38 -0
  111. package/corpus/templates/slides/server/plugins/db.ts +20 -0
  112. package/corpus/templates/slides/shared/design-system-validation.ts +62 -0
  113. package/dist/a2a/auth-policy.js +1 -1
  114. package/dist/a2a/auth-policy.js.map +1 -1
  115. package/dist/agent/production-agent.d.ts +1 -0
  116. package/dist/agent/production-agent.d.ts.map +1 -1
  117. package/dist/agent/production-agent.js +12 -1
  118. package/dist/agent/production-agent.js.map +1 -1
  119. package/dist/agent/run-manager.d.ts +2 -0
  120. package/dist/agent/run-manager.d.ts.map +1 -1
  121. package/dist/agent/run-manager.js +4 -10
  122. package/dist/agent/run-manager.js.map +1 -1
  123. package/dist/cli/index.js +8 -1
  124. package/dist/cli/index.js.map +1 -1
  125. package/dist/client/route-chunk-recovery.d.ts.map +1 -1
  126. package/dist/client/route-chunk-recovery.js +16 -7
  127. package/dist/client/route-chunk-recovery.js.map +1 -1
  128. package/dist/collab/awareness.d.ts +2 -2
  129. package/dist/collab/awareness.d.ts.map +1 -1
  130. package/dist/collab/routes.d.ts +1 -1
  131. package/dist/collab/struct-routes.d.ts +1 -1
  132. package/dist/db/client.d.ts.map +1 -1
  133. package/dist/db/client.js +4 -2
  134. package/dist/db/client.js.map +1 -1
  135. package/dist/db/migrations.d.ts.map +1 -1
  136. package/dist/db/migrations.js +2 -1
  137. package/dist/db/migrations.js.map +1 -1
  138. package/dist/deploy/build.d.ts +25 -0
  139. package/dist/deploy/build.d.ts.map +1 -1
  140. package/dist/deploy/build.js +227 -4
  141. package/dist/deploy/build.js.map +1 -1
  142. package/dist/deploy/nitro-preset.d.ts +8 -0
  143. package/dist/deploy/nitro-preset.d.ts.map +1 -0
  144. package/dist/deploy/nitro-preset.js +36 -0
  145. package/dist/deploy/nitro-preset.js.map +1 -0
  146. package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
  147. package/dist/integrations/pending-tasks-retry-job.js +2 -1
  148. package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
  149. package/dist/notifications/routes.d.ts +3 -3
  150. package/dist/observability/routes.d.ts +3 -3
  151. package/dist/progress/routes.d.ts +1 -1
  152. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  153. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  154. package/dist/scripts/call-agent.d.ts.map +1 -1
  155. package/dist/scripts/call-agent.js +4 -2
  156. package/dist/scripts/call-agent.js.map +1 -1
  157. package/dist/secrets/routes.d.ts +3 -3
  158. package/dist/server/agent-discovery.d.ts.map +1 -1
  159. package/dist/server/agent-discovery.js +1 -0
  160. package/dist/server/agent-discovery.js.map +1 -1
  161. package/dist/server/agent-run-context.d.ts.map +1 -1
  162. package/dist/server/agent-run-context.js +10 -3
  163. package/dist/server/agent-run-context.js.map +1 -1
  164. package/dist/server/request-context.d.ts +2 -0
  165. package/dist/server/request-context.d.ts.map +1 -1
  166. package/dist/server/request-context.js.map +1 -1
  167. package/dist/server/transcribe-voice.d.ts +1 -1
  168. package/dist/shared/runtime.d.ts.map +1 -1
  169. package/dist/shared/runtime.js +2 -0
  170. package/dist/shared/runtime.js.map +1 -1
  171. package/dist/templates/default/.agents/skills/secrets/SKILL.md +29 -0
  172. package/dist/templates/default/app/i18n/ar-SA.ts +6 -6
  173. package/dist/templates/default/app/i18n/de-DE.ts +6 -6
  174. package/dist/templates/default/app/i18n/en-US.ts +6 -6
  175. package/dist/templates/default/app/i18n/es-ES.ts +6 -6
  176. package/dist/templates/default/app/i18n/fr-FR.ts +6 -6
  177. package/dist/templates/default/app/i18n/hi-IN.ts +6 -6
  178. package/dist/templates/default/app/i18n/ja-JP.ts +6 -6
  179. package/dist/templates/default/app/i18n/ko-KR.ts +6 -6
  180. package/dist/templates/default/app/i18n/pt-BR.ts +6 -6
  181. package/dist/templates/default/app/i18n/zh-CN.ts +6 -6
  182. package/dist/templates/default/app/i18n/zh-TW.ts +6 -6
  183. package/dist/templates/headless/.agents/skills/secrets/SKILL.md +29 -0
  184. package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -0
  185. package/dist/vite/client.d.ts +1 -1
  186. package/dist/vite/client.d.ts.map +1 -1
  187. package/dist/vite/client.js +16 -0
  188. package/dist/vite/client.js.map +1 -1
  189. package/docs/content/cloneable-saas.mdx +9 -9
  190. package/docs/content/creating-templates.mdx +2 -2
  191. package/docs/content/deployment.mdx +23 -0
  192. package/docs/content/faq.mdx +4 -4
  193. package/docs/content/key-concepts.mdx +1 -1
  194. package/docs/content/multi-app-workspace.mdx +1 -1
  195. package/docs/content/syncing-template-changes.mdx +1 -1
  196. package/docs/content/template-analytics-developers.mdx +2 -2
  197. package/docs/content/template-analytics.mdx +2 -2
  198. package/docs/content/template-assets-developers.mdx +3 -3
  199. package/docs/content/template-assets.mdx +2 -2
  200. package/docs/content/template-brain-developers.mdx +2 -2
  201. package/docs/content/template-brain.mdx +1 -1
  202. package/docs/content/template-calendar-developers.mdx +1 -1
  203. package/docs/content/template-calendar.mdx +1 -1
  204. package/docs/content/template-chat.mdx +1 -1
  205. package/docs/content/template-clips-developers.mdx +4 -4
  206. package/docs/content/template-clips.mdx +2 -2
  207. package/docs/content/template-content-developers.mdx +2 -2
  208. package/docs/content/template-content.mdx +1 -1
  209. package/docs/content/template-design-brand-and-figma.mdx +1 -1
  210. package/docs/content/template-design-developers.mdx +2 -2
  211. package/docs/content/template-design.mdx +2 -2
  212. package/docs/content/template-dispatch-developers.mdx +5 -5
  213. package/docs/content/template-forms-developers.mdx +1 -1
  214. package/docs/content/template-forms.mdx +1 -1
  215. package/docs/content/template-mail-developers.mdx +2 -2
  216. package/docs/content/template-mail.mdx +2 -2
  217. package/docs/content/template-plan-developers.mdx +3 -3
  218. package/docs/content/template-plan.mdx +3 -3
  219. package/docs/content/template-slides-developers.mdx +4 -4
  220. package/docs/content/template-slides.mdx +1 -1
  221. package/docs/content/what-is-agent-native.mdx +5 -5
  222. package/package.json +1 -1
  223. package/src/a2a/auth-policy.ts +1 -1
  224. package/src/agent/production-agent.ts +14 -1
  225. package/src/agent/run-manager.ts +6 -13
  226. package/src/cli/index.ts +11 -1
  227. package/src/client/route-chunk-recovery.ts +18 -7
  228. package/src/db/client.ts +7 -6
  229. package/src/db/migrations.ts +2 -1
  230. package/src/deploy/build.ts +290 -4
  231. package/src/deploy/nitro-preset.ts +50 -0
  232. package/src/integrations/pending-tasks-retry-job.ts +2 -1
  233. package/src/scripts/call-agent.ts +4 -2
  234. package/src/server/agent-discovery.ts +1 -0
  235. package/src/server/agent-run-context.ts +13 -3
  236. package/src/server/request-context.ts +2 -0
  237. package/src/shared/cloudflare-globals.d.ts +2 -1
  238. package/src/shared/runtime.ts +2 -0
  239. package/src/templates/default/.agents/skills/secrets/SKILL.md +29 -0
  240. package/src/templates/default/app/i18n/ar-SA.ts +6 -6
  241. package/src/templates/default/app/i18n/de-DE.ts +6 -6
  242. package/src/templates/default/app/i18n/en-US.ts +6 -6
  243. package/src/templates/default/app/i18n/es-ES.ts +6 -6
  244. package/src/templates/default/app/i18n/fr-FR.ts +6 -6
  245. package/src/templates/default/app/i18n/hi-IN.ts +6 -6
  246. package/src/templates/default/app/i18n/ja-JP.ts +6 -6
  247. package/src/templates/default/app/i18n/ko-KR.ts +6 -6
  248. package/src/templates/default/app/i18n/pt-BR.ts +6 -6
  249. package/src/templates/default/app/i18n/zh-CN.ts +6 -6
  250. package/src/templates/default/app/i18n/zh-TW.ts +6 -6
  251. package/src/templates/headless/.agents/skills/secrets/SKILL.md +29 -0
  252. package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -0
  253. package/src/vite/client.ts +20 -1
  254. /package/corpus/templates/slides/server/routes/api/assets/{[filename].delete.ts → [id].delete.ts} +0 -0
@@ -322,19 +322,19 @@ Agent-native isn't just for developers. Because the agent can edit the app's own
322
322
 
323
323
  The vision: fewer handoffs, one person doing the work of a small team.
324
324
 
325
- ## Fork and customize {#fork-and-customize}
325
+ ## Start from a template and customize {#fork-and-customize}
326
326
 
327
- Agent-native apps follow a fork-and-customize model. You start from a **template** — Calendar, Content, Slides, Analytics, Mail, Clips, Design, Forms, Dispatch — and make it yours. Each one is a complete, working SaaS product you fork wholesale, not a blank scaffold:
327
+ Agent-native apps follow a start-from-a-working-app model. You start from a **template** — Calendar, Content, Slides, Analytics, Mail, Clips, Design, Forms, Dispatch — and make it yours. Each one is a complete, working SaaS product, not a blank scaffold:
328
328
 
329
329
  1. Pick a template on [agent-native.com/templates](/templates)
330
330
  2. Use it immediately as a hosted app (e.g. mail.agent-native.com)
331
- 3. Fork it when you want to customize — "connect our Stripe account," "add a cohort chart"
331
+ 3. Create your own app from the template when you want to customize — "connect our Stripe account," "add a cohort chart"
332
332
  4. The agent modifies the code to match your needs
333
- 5. Deploy your fork to your own domain — or stay on agent-native.com
333
+ 5. Deploy your app to your own domain — or stay on agent-native.com
334
334
 
335
335
  Because it's _your_ app, not shared infrastructure, the agent can safely evolve the code. Your app keeps improving as you use it. See [Templates](/docs/cloneable-saas) for the full story.
336
336
 
337
- Not ready to fork a whole template? You can also try agent-native by adding a **skill** to a coding agent you already use — install the Plans skill with `npx @agent-native/core@latest skills add visual-plan`. See the [Skills Guide](/docs/skills-guide#app-backed-skills).
337
+ Not ready to start from a whole template? You can also try agent-native by adding a **skill** to a coding agent you already use — install the Plans skill with `npx @agent-native/core@latest skills add visual-plan`. See the [Skills Guide](/docs/skills-guide#app-backed-skills).
338
338
 
339
339
  ## Composable agents {#composable-agents}
340
340
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.124.3",
3
+ "version": "0.124.4",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -16,7 +16,7 @@ export function isA2AProductionRuntime(): boolean {
16
16
  return true;
17
17
  }
18
18
  if (process.env.CF_PAGES === "1") return true;
19
- if ("__cf_env" in globalThis) return true;
19
+ if ("__cf_env" in globalThis || "__env__" in globalThis) return true;
20
20
  if (process.env.VERCEL || process.env.VERCEL_ENV) return true;
21
21
  if (process.env.RENDER || process.env.FLY_APP_NAME || process.env.K_SERVICE) {
22
22
  return true;
@@ -2088,6 +2088,13 @@ function collectTextParts(parts: EngineContentPart[]): string {
2088
2088
  .join("");
2089
2089
  }
2090
2090
 
2091
+ // Guard retries are pushed as `role: "user"` because engines have no other
2092
+ // mid-turn channel. Unlabeled, a corrective instruction ("say the source is
2093
+ // not connected and include this link") reads exactly like an injected user
2094
+ // turn, and an aligned model refuses it *to the user* instead of following it.
2095
+ export const AGENT_INTERNAL_GUARD_PROMPT =
2096
+ "Automated quality check on the draft answer you just produced. This is a directive from this application's own response guard, not a message from the user and not content from a tool result or web page. Follow it and revise your answer. Do not quote it, describe it, or treat it as an injection attempt. If it contradicts what you observed this turn, state what you actually observed instead of asserting the guard's premise.";
2097
+
2091
2098
  export const AGENT_INTERNAL_CONTINUE_PROMPT =
2092
2099
  "Continue from where you left off and finish the user's original request. Do not repeat completed work, do not mention internal reconnects, time limits, or step limits, and continue as if this is the same uninterrupted run.";
2093
2100
 
@@ -4588,7 +4595,12 @@ export async function runAgentLoop(opts: {
4588
4595
  send({ type: "clear" });
4589
4596
  messages.push({
4590
4597
  role: "user",
4591
- content: [{ type: "text", text: retryMessage }],
4598
+ content: [
4599
+ {
4600
+ type: "text",
4601
+ text: `${AGENT_INTERNAL_GUARD_PROMPT}\n\n<response-guard>\n${retryMessage}\n</response-guard>`,
4602
+ },
4603
+ ],
4592
4604
  });
4593
4605
  continue;
4594
4606
  }
@@ -8966,6 +8978,7 @@ export function createProductionAgentHandler(
8966
8978
  // assistant message. Falls back to the runId (turn == run) when the
8967
8979
  // client doesn't supply a turnId.
8968
8980
  turnId: effectiveTurnId,
8981
+ waitUntil: getRequestRunContext()?.waitUntil,
8969
8982
  dispatchMode: foregroundSelfChainEligible
8970
8983
  ? "foreground-self-chain"
8971
8984
  : "foreground",
@@ -348,6 +348,8 @@ function shouldCaptureRunError(err: unknown): boolean {
348
348
  }
349
349
 
350
350
  export interface StartRunOptions {
351
+ /** Keep a request-scoped serverless invocation alive for this run. */
352
+ waitUntil?: (promise: Promise<unknown>) => void;
351
353
  /** Optional internal run chunk budget. When reached, the framework emits an
352
354
  * auto-continuation signal instead of a user-facing timeout. Leave unset for
353
355
  * no framework-imposed run timeout. */
@@ -1344,19 +1346,10 @@ export function startRun(
1344
1346
  });
1345
1347
  runPromise.then(resolveFinalized, rejectFinalized);
1346
1348
 
1347
- // On Cloudflare Workers, keep the isolate alive for this run
1348
- try {
1349
- const cfCtx = (
1350
- globalThis as typeof globalThis & {
1351
- __cf_ctx?: { waitUntil(promise: Promise<unknown>): void };
1352
- }
1353
- ).__cf_ctx;
1354
- if (cfCtx?.waitUntil) {
1355
- cfCtx.waitUntil(runPromise);
1356
- }
1357
- } catch {
1358
- // Not on Workers — ignore
1359
- }
1349
+ // Keep the originating request alive when its runtime supports background
1350
+ // work. The callback is passed through request context so concurrent Worker
1351
+ // requests cannot overwrite one another through a global binding.
1352
+ options?.waitUntil?.(runPromise);
1360
1353
 
1361
1354
  return run;
1362
1355
  }
package/src/cli/index.ts CHANGED
@@ -8,6 +8,10 @@ import { fileURLToPath } from "url";
8
8
 
9
9
  import * as Sentry from "@sentry/node";
10
10
 
11
+ import {
12
+ clearAgentNativeNitroPresetMarker,
13
+ resolveAgentNativeNitroPreset,
14
+ } from "../deploy/nitro-preset.js";
11
15
  import { resolveDeployPostBuildInvocation } from "./deploy-build.js";
12
16
  import { shouldTrackCliRun } from "./telemetry-routing.js";
13
17
  import { createCliTelemetry } from "./telemetry.js";
@@ -696,6 +700,7 @@ switch (command) {
696
700
  }
697
701
 
698
702
  if (isReactRouterFramework()) {
703
+ clearAgentNativeNitroPresetMarker();
699
704
  validateReactRouterBuildDependencies();
700
705
  const rr = findReactRouterBin();
701
706
  console.log("Building (React Router framework mode)...");
@@ -709,15 +714,20 @@ switch (command) {
709
714
  // Post-build: framework-mode apps also need a Nitro server bundle for
710
715
  // `agent-native start` and for serverless presets.
711
716
  if (isReactRouterFramework()) {
717
+ const configuredNitroPreset = resolveAgentNativeNitroPreset();
718
+ const deployEnv = configuredNitroPreset
719
+ ? { ...process.env, NITRO_PRESET: configuredNitroPreset }
720
+ : process.env;
712
721
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
713
722
  const deployBuild = resolveDeployPostBuildInvocation({
714
723
  cliDir: __dirname,
724
+ env: deployEnv,
715
725
  findTsxBin,
716
726
  });
717
727
  if (deployBuild) {
718
728
  await runBuildStep(deployBuild.command, deployBuild.args, {
719
729
  label: "deploy-build",
720
- env: process.env,
730
+ env: deployEnv,
721
731
  });
722
732
  } else {
723
733
  console.warn(
@@ -190,16 +190,27 @@ function recoverToIntendedNavigation(
190
190
  state: RouteChunkRecoveryState,
191
191
  ): boolean {
192
192
  const target = getFreshIntendedNavigation(state, win.location.href);
193
- if (!target) return false;
193
+ const sameCurrentTarget =
194
+ !target &&
195
+ state.intendedHref === win.location.href &&
196
+ Date.now() - state.intendedAt <= INTENDED_NAV_MAX_AGE_MS
197
+ ? state.intendedHref
198
+ : null;
199
+ const recoveryTarget = target ?? sameCurrentTarget;
200
+ if (!recoveryTarget) return false;
194
201
  state.recovering = true;
195
- state.recoveryHref = target;
196
- // Desktop webviews stay open across many deploys; a forced navigation here
197
- // reads as a random tab reload. Leave the current view alive instead.
198
- if (isAgentNativeDesktop(win)) return true;
202
+ state.recoveryHref = recoveryTarget;
203
+ // Keep the desktop shell mounted, but replace only the route that failed to
204
+ // load. A current-page reload remains suppressed below when there is no
205
+ // intended cross-route destination to recover.
206
+ if (isAgentNativeDesktop(win)) {
207
+ hardNavigate(win, recoveryTarget);
208
+ return true;
209
+ }
199
210
  try {
200
- win.history.replaceState(win.history.state, "", target);
211
+ win.history.replaceState(win.history.state, "", recoveryTarget);
201
212
  } catch {}
202
- hardNavigate(win, target);
213
+ hardNavigate(win, recoveryTarget);
203
214
  return true;
204
215
  }
205
216
 
package/src/db/client.ts CHANGED
@@ -65,11 +65,11 @@ export interface DbExecConfig {
65
65
  /** Read the request-scoped Cloudflare binding without requiring every
66
66
  * consuming app's TypeScript program to include core's ambient Worker globals. */
67
67
  export function getCloudflareD1Binding(): unknown {
68
- return (
69
- globalThis as typeof globalThis & {
70
- __cf_env?: { DB?: unknown };
71
- }
72
- ).__cf_env?.DB;
68
+ const runtime = globalThis as typeof globalThis & {
69
+ __cf_env?: { DB?: unknown };
70
+ __env__?: { DB?: unknown };
71
+ };
72
+ return runtime.__cf_env?.DB ?? runtime.__env__?.DB;
73
73
  }
74
74
 
75
75
  // ---------------------------------------------------------------------------
@@ -451,7 +451,8 @@ export function getDialect(): Dialect {
451
451
  return _dialect;
452
452
  }
453
453
 
454
- // Don't cache the fallthrough — on CF Workers, env bindings (__cf_env) aren't
454
+ // Don't cache the fallthrough — on CF Workers, env bindings (__cf_env/__env__)
455
+ // aren't
455
456
  // available at import time. If we cache "sqlite" here, D1 will never be
456
457
  // detected once the bindings are set in the fetch handler.
457
458
  return "sqlite";
@@ -682,7 +682,8 @@ export function runMigrations(
682
682
  !!globalThis.process?.env?.NETLIFY ||
683
683
  !!globalThis.process?.env?.AWS_LAMBDA_FUNCTION_NAME ||
684
684
  !!globalThis.process?.env?.VERCEL ||
685
- "__cf_env" in globalThis;
685
+ "__cf_env" in globalThis ||
686
+ "__env__" in globalThis;
686
687
  if (typeof globalThis.process?.exit === "function" && !isServerless) {
687
688
  process.exit(1);
688
689
  }
@@ -10,6 +10,7 @@
10
10
  *
11
11
  * Supported presets:
12
12
  * - cloudflare_pages: Outputs dist/ with _worker.js for Cloudflare Pages
13
+ * - cloudflare_module: Outputs a native Cloudflare Worker under .output/server
13
14
  *
14
15
  * Usage: node deploy/build.js (called automatically by `agent-native build`)
15
16
  */
@@ -73,6 +74,171 @@ import {
73
74
 
74
75
  const cwd = process.cwd();
75
76
  const preset = process.env.NITRO_PRESET || "node";
77
+ export const CLOUDFLARE_MODULE_PRESETS = [
78
+ "cloudflare_module",
79
+ "cloudflare-module",
80
+ ] as const;
81
+
82
+ export function isCloudflareModulePreset(targetPreset: string): boolean {
83
+ return (CLOUDFLARE_MODULE_PRESETS as readonly string[]).includes(
84
+ targetPreset,
85
+ );
86
+ }
87
+
88
+ export const CLOUDFLARE_MODULE_WORKER_ENTRY = "worker.mjs";
89
+
90
+ export function generateCloudflareModuleWorkerEntry(): string {
91
+ return `let handler;
92
+
93
+ export * from "./index.mjs";
94
+
95
+ async function loadHandler() {
96
+ handler ??= (await import("./index.mjs")).default;
97
+ return handler;
98
+ }
99
+
100
+ function initializeBindings(env) {
101
+ if (!env) return;
102
+ globalThis.__cf_env = env;
103
+ globalThis.__env__ = env;
104
+ globalThis.process = globalThis.process || { env: {} };
105
+ globalThis.process.env = globalThis.process.env || {};
106
+ for (const [key, value] of Object.entries(env)) {
107
+ if (typeof value === "string") globalThis.process.env[key] = value;
108
+ }
109
+ }
110
+
111
+ export default {
112
+ async fetch(request, env, ctx) {
113
+ if (typeof ctx?.waitUntil === "function") {
114
+ request.waitUntil = ctx.waitUntil.bind(ctx);
115
+ }
116
+ initializeBindings(env);
117
+ return (await loadHandler()).fetch(request, env, ctx);
118
+ },
119
+ async scheduled(controller, env, ctx) {
120
+ initializeBindings(env);
121
+ return (await loadHandler()).scheduled?.(controller, env, ctx);
122
+ },
123
+ async email(message, env, ctx) {
124
+ initializeBindings(env);
125
+ return (await loadHandler()).email?.(message, env, ctx);
126
+ },
127
+ async queue(batch, env, ctx) {
128
+ initializeBindings(env);
129
+ return (await loadHandler()).queue?.(batch, env, ctx);
130
+ },
131
+ async tail(traces, env, ctx) {
132
+ initializeBindings(env);
133
+ return (await loadHandler()).tail?.(traces, env, ctx);
134
+ },
135
+ async trace(traces, env, ctx) {
136
+ initializeBindings(env);
137
+ return (await loadHandler()).trace?.(traces, env, ctx);
138
+ },
139
+ };
140
+ `;
141
+ }
142
+
143
+ export function patchCloudflareModuleNitroEntry(code: string): string {
144
+ const factoryMatch = code.match(
145
+ /function ([A-Za-z_$][\w$]*)\(e\)\{let ([A-Za-z_$][\w$]*)=([A-Za-z_$][\w$]*)\(\),([A-Za-z_$][\w$]*)=([A-Za-z_$][\w$]*)\(\);return\{async fetch\(([A-Za-z_$][\w$]*),([A-Za-z_$][\w$]*),([A-Za-z_$][\w$]*)\)\{/,
146
+ );
147
+ if (!factoryMatch) {
148
+ throw new Error(
149
+ "[deploy] Could not find Nitro's Cloudflare module handler factory",
150
+ );
151
+ }
152
+
153
+ const [
154
+ ,
155
+ factoryName,
156
+ handlerName,
157
+ handlerFactoryName,
158
+ hooksName,
159
+ hooksFactoryName,
160
+ requestName,
161
+ envName,
162
+ contextName,
163
+ ] = factoryMatch;
164
+ const eagerInitialization = `let ${handlerName}=${handlerFactoryName}(),${hooksName}=${hooksFactoryName}();`;
165
+ if (!code.includes(eagerInitialization)) {
166
+ throw new Error(
167
+ `[deploy] Nitro's ${factoryName} handler changed its initialization shape`,
168
+ );
169
+ }
170
+
171
+ const bindingMatch = code.match(
172
+ new RegExp(
173
+ `globalThis\\.__env__=${envName},([A-Za-z_$][\\w$]*)\\(${requestName},\\{env:${envName},context:${contextName}\\}\\);?`,
174
+ ),
175
+ );
176
+ if (!bindingMatch) {
177
+ throw new Error(
178
+ `[deploy] Nitro's ${factoryName} handler does not initialize Cloudflare bindings as expected`,
179
+ );
180
+ }
181
+ const bindingInitialization = bindingMatch[0];
182
+
183
+ let patched = code.replace(
184
+ eagerInitialization,
185
+ `let ${handlerName},${hooksName};`,
186
+ );
187
+ patched = patched.replace(
188
+ bindingInitialization,
189
+ `${bindingInitialization}${handlerName}??=${handlerFactoryName}();`,
190
+ );
191
+ const hookCall = `${hooksName}.callHook(`;
192
+ if (!patched.includes(hookCall)) {
193
+ throw new Error(
194
+ `[deploy] Nitro's ${factoryName} handler has no Cloudflare lifecycle hooks`,
195
+ );
196
+ }
197
+ patched = patched
198
+ .split(hookCall)
199
+ .join(`(${hooksName}??=${hooksFactoryName}()).callHook(`);
200
+
201
+ return patched;
202
+ }
203
+
204
+ export function configureCloudflareModuleWorkerOutput(serverDir: string): void {
205
+ const configPath = path.join(serverDir, "wrangler.json");
206
+ if (!fs.existsSync(configPath)) {
207
+ throw new Error(
208
+ `[deploy] Nitro did not generate ${configPath} for cloudflare_module`,
209
+ );
210
+ }
211
+ const nitroEntryPath = path.join(serverDir, "index.mjs");
212
+ if (!fs.existsSync(nitroEntryPath)) {
213
+ throw new Error(
214
+ `[deploy] Nitro did not generate ${nitroEntryPath} for cloudflare_module`,
215
+ );
216
+ }
217
+
218
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8")) as {
219
+ main?: string;
220
+ compatibility_flags?: unknown;
221
+ [key: string]: unknown;
222
+ };
223
+ config.main = CLOUDFLARE_MODULE_WORKER_ENTRY;
224
+ const compatibilityFlags = Array.isArray(config.compatibility_flags)
225
+ ? config.compatibility_flags.filter(
226
+ (flag): flag is string => typeof flag === "string",
227
+ )
228
+ : [];
229
+ config.compatibility_flags = [
230
+ ...new Set([...compatibilityFlags, "nodejs_compat"]),
231
+ ];
232
+ fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
233
+ fs.writeFileSync(
234
+ nitroEntryPath,
235
+ patchCloudflareModuleNitroEntry(fs.readFileSync(nitroEntryPath, "utf8")),
236
+ );
237
+ fs.writeFileSync(
238
+ path.join(serverDir, CLOUDFLARE_MODULE_WORKER_ENTRY),
239
+ generateCloudflareModuleWorkerEntry(),
240
+ );
241
+ }
76
242
  export const NITRO_RUNTIME_IGNORE_PATTERNS = [
77
243
  "**/*.spec.ts",
78
244
  "**/*.spec.tsx",
@@ -893,7 +1059,9 @@ function requestWithMountedApiPrefixStripped(request) {
893
1059
  return request;
894
1060
  }
895
1061
  url.pathname = strippedPathname;
896
- return new Request(url, request);
1062
+ const rewritten = new Request(url, request);
1063
+ rewritten.waitUntil = request.waitUntil;
1064
+ return rewritten;
897
1065
  }
898
1066
 
899
1067
  function prefixMountedPath(path, basePath) {
@@ -1347,8 +1515,10 @@ ${
1347
1515
 
1348
1516
  export default {
1349
1517
  async fetch(request, env, ctx) {
1350
- // Expose env and ctx bindings globally for compatibility
1351
- if (ctx) globalThis.__cf_ctx = ctx;
1518
+ // Attach the request-scoped continuation hook before any URL rewrite.
1519
+ if (typeof ctx?.waitUntil === "function") {
1520
+ request.waitUntil = ctx.waitUntil.bind(ctx);
1521
+ }
1352
1522
  if (env) {
1353
1523
  globalThis.process = globalThis.process || { env: {} };
1354
1524
  globalThis.process.env = globalThis.process.env || {};
@@ -2959,6 +3129,23 @@ export function bundleYjsRuntimeForServerlessOutput(
2959
3129
  );
2960
3130
  }
2961
3131
 
3132
+ walkServerJavaScriptFiles(serverDir, (filePath) => {
3133
+ const bundledImport = path
3134
+ .relative(path.dirname(filePath), bundledYjsPath)
3135
+ .split(path.sep)
3136
+ .join("/");
3137
+ const relativeBundledImport = bundledImport.startsWith(".")
3138
+ ? bundledImport
3139
+ : `./${bundledImport}`;
3140
+ const source = fs.readFileSync(filePath, "utf-8");
3141
+ const rewritten = source.replace(
3142
+ /(\b(?:from\s*|import\s*\(\s*|import\s*))(["'])\.\.?\/(?:\.\.\/)*_libs\/yjs\.mjs\2/g,
3143
+ (_match, importPrefix: string, quote: string) =>
3144
+ `${importPrefix}${quote}${relativeBundledImport}${quote}`,
3145
+ );
3146
+ if (rewritten !== source) fs.writeFileSync(filePath, rewritten);
3147
+ });
3148
+
2962
3149
  return bareImports;
2963
3150
  }
2964
3151
 
@@ -3601,6 +3788,53 @@ const BROWSER_ONLY_SERVER_LIBS = [
3601
3788
  "mermaid",
3602
3789
  ];
3603
3790
 
3791
+ /**
3792
+ * Packages that Nitro can discover through the shared server graph but that
3793
+ * cannot be evaluated in a Cloudflare Worker. Keep the network-capable SDKs
3794
+ * real; these are limited to Node/native/browser-runtime packages whose
3795
+ * server-side paths already fail closed when the capability is unavailable.
3796
+ */
3797
+ export const CLOUDFLARE_MODULE_STUB_MODULES = [
3798
+ "@napi-rs/canvas",
3799
+ "@resvg/resvg-js",
3800
+ "@sentry/node",
3801
+ "@sparticuz/chromium-min",
3802
+ "better-sqlite3",
3803
+ "chartjs-node-canvas",
3804
+ "chokidar",
3805
+ "fsevents",
3806
+ "node-pty",
3807
+ "playwright",
3808
+ "playwright-core",
3809
+ ] as const;
3810
+
3811
+ /**
3812
+ * Mirror the fail-closed package stubs used by the Pages bundler in Nitro's
3813
+ * Rolldown graph. Without this, the native module preset emits a valid module
3814
+ * graph that still crashes at Worker cold start when a Node-only optional
3815
+ * dependency is evaluated.
3816
+ */
3817
+ export function createCloudflareModuleStubPlugin() {
3818
+ const stubbed = new Set<string>(CLOUDFLARE_MODULE_STUB_MODULES);
3819
+ const stubIdPrefix = "\0agent-native-cloudflare-module-stub:";
3820
+
3821
+ return {
3822
+ name: "agent-native-cloudflare-module-stub",
3823
+ resolveId(id: string) {
3824
+ const packageName = id.startsWith("@")
3825
+ ? id.split("/").slice(0, 2).join("/")
3826
+ : id.split("/")[0];
3827
+ if (!stubbed.has(packageName) || id !== packageName) return null;
3828
+ return `${stubIdPrefix}${packageName}`;
3829
+ },
3830
+ load(id: string) {
3831
+ if (!id.startsWith(stubIdPrefix)) return null;
3832
+ const packageName = id.slice(stubIdPrefix.length);
3833
+ return CLOUDFLARE_WORKER_STUB_MODULES[packageName] ?? null;
3834
+ },
3835
+ };
3836
+ }
3837
+
3604
3838
  /**
3605
3839
  * Dependencies Nitro itself must bundle outside the controlled serverless
3606
3840
  * output pass. Netlify, Vercel, and Lambda keep Yjs external through Nitro;
@@ -3811,7 +4045,12 @@ export default bundle;
3811
4045
  ...(preset === "netlify" || preset === "vercel" || preset === "aws-lambda"
3812
4046
  ? { external: ["yjs"] }
3813
4047
  : {}),
3814
- plugins: [createBrowserOnlyServerStubPlugin()],
4048
+ plugins: [
4049
+ ...(preset.startsWith("cloudflare")
4050
+ ? [createCloudflareModuleStubPlugin()]
4051
+ : []),
4052
+ createBrowserOnlyServerStubPlugin(),
4053
+ ],
3815
4054
  },
3816
4055
  ...(providedPluginsNitroPlugin
3817
4056
  ? { plugins: [providedPluginsNitroPlugin] }
@@ -3833,6 +4072,10 @@ export default bundle;
3833
4072
  cwd,
3834
4073
  });
3835
4074
 
4075
+ if (isCloudflareModulePreset(preset)) {
4076
+ configureCloudflareModuleWorkerOutput(nitro.options.output.serverDir);
4077
+ }
4078
+
3836
4079
  if (preset === "netlify" || preset === "vercel" || preset === "aws-lambda") {
3837
4080
  copyInstalledLibsqlNativePackages(nitro.options.output.serverDir);
3838
4081
  copyInstalledResvgPackages(nitro.options.output.serverDir);
@@ -3841,6 +4084,10 @@ export default bundle;
3841
4084
  bundleYjsRuntimeForServerlessOutput(nitro.options.output.serverDir, cwd);
3842
4085
  }
3843
4086
 
4087
+ if (isCloudflareModulePreset(preset)) {
4088
+ bundleYjsRuntimeForServerlessOutput(nitro.options.output.serverDir, cwd);
4089
+ }
4090
+
3844
4091
  if (preset === "netlify") {
3845
4092
  emitSingleTemplateNetlifyKeepWarmFunction(cwd);
3846
4093
 
@@ -3949,8 +4196,43 @@ export default bundle;
3949
4196
  if (bareImports.size > 0) {
3950
4197
  const libsDir = path.join(outputDir, "_libs");
3951
4198
  fs.mkdirSync(libsDir, { recursive: true });
4199
+ function rewriteExternalImports(mod: string, outFile: string) {
4200
+ function rewriteImports(dir: string) {
4201
+ if (!fs.existsSync(dir)) return;
4202
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
4203
+ const p = path.join(dir, entry.name);
4204
+ if (entry.isDirectory()) {
4205
+ rewriteImports(p);
4206
+ continue;
4207
+ }
4208
+ if (!entry.name.endsWith(".mjs") && !entry.name.endsWith(".js"))
4209
+ continue;
4210
+ const code = fs.readFileSync(p, "utf8");
4211
+ const relPath = path
4212
+ .relative(path.dirname(p), outFile)
4213
+ .replace(/\\/g, "/");
4214
+ const importPath = relPath.startsWith(".")
4215
+ ? relPath
4216
+ : "./" + relPath;
4217
+ const escaped = mod.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4218
+ const re = new RegExp(`from["']${escaped}["']`, "g");
4219
+ const rewritten = code.replace(re, `from"${importPath}"`);
4220
+ if (rewritten !== code) fs.writeFileSync(p, rewritten);
4221
+ }
4222
+ }
4223
+ rewriteImports(outputDir);
4224
+ }
3952
4225
  for (const mod of bareImports) {
3953
4226
  const outFile = path.join(libsDir, `${mod.replace(/[/@]/g, "_")}.mjs`);
4227
+ // Nitro may already have emitted a correctly minified module wrapper
4228
+ // for this dependency. Replacing that wrapper with an esbuild CJS
4229
+ // adapter loses its public export aliases and makes otherwise valid
4230
+ // sibling chunks fail during Worker module linking.
4231
+ if (fs.existsSync(outFile)) {
4232
+ console.log(`[deploy] Retaining Nitro external: ${mod}`);
4233
+ rewriteExternalImports(mod, outFile);
4234
+ continue;
4235
+ }
3954
4236
  try {
3955
4237
  // Resolve the module — check workspace node_modules and pnpm store
3956
4238
  let resolvedMod = mod;
@@ -4244,6 +4526,10 @@ async function main() {
4244
4526
  // as multi-module Workers. Use the custom esbuild-based bundler.
4245
4527
  await buildCloudflarePages();
4246
4528
  break;
4529
+ case "cloudflare_module":
4530
+ case "cloudflare-module":
4531
+ await buildWithNitro();
4532
+ break;
4247
4533
  default:
4248
4534
  // All other presets (netlify, vercel, deno_deploy, aws-lambda, etc.)
4249
4535
  // are handled natively by Nitro's build API.
@@ -0,0 +1,50 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ export const AGENT_NATIVE_NITRO_PRESET_MARKER = path.join(
5
+ ".agent-native",
6
+ "nitro-preset",
7
+ );
8
+
9
+ function markerPath(cwd: string): string {
10
+ return path.join(cwd, AGENT_NATIVE_NITRO_PRESET_MARKER);
11
+ }
12
+
13
+ export function clearAgentNativeNitroPresetMarker(cwd = process.cwd()): void {
14
+ fs.rmSync(markerPath(cwd), { force: true });
15
+ }
16
+
17
+ export function writeAgentNativeNitroPresetMarker(
18
+ preset: string,
19
+ cwd = process.cwd(),
20
+ ): void {
21
+ const normalizedPreset = preset.trim();
22
+ if (!normalizedPreset) return;
23
+
24
+ const filePath = markerPath(cwd);
25
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
26
+ fs.writeFileSync(filePath, `${normalizedPreset}\n`);
27
+ }
28
+
29
+ export function resolveAgentNativeNitroPreset({
30
+ cwd = process.cwd(),
31
+ env = process.env,
32
+ }: {
33
+ cwd?: string;
34
+ env?: NodeJS.ProcessEnv;
35
+ } = {}): string | undefined {
36
+ const environmentPreset = env.NITRO_PRESET?.trim();
37
+ if (environmentPreset) return environmentPreset;
38
+
39
+ const filePath = markerPath(cwd);
40
+ try {
41
+ const marker = fs.readFileSync(filePath, "utf8").trim();
42
+ if (!marker) {
43
+ throw new Error(`Agent-Native Nitro preset marker is empty: ${filePath}`);
44
+ }
45
+ return marker;
46
+ } catch (error) {
47
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
48
+ throw error;
49
+ }
50
+ }
@@ -323,7 +323,8 @@ function getProcessingStuckAfterMs(): number {
323
323
  process.env.NETLIFY ||
324
324
  process.env.AWS_LAMBDA_FUNCTION_NAME ||
325
325
  process.env.VERCEL ||
326
- "__cf_env" in globalThis
326
+ "__cf_env" in globalThis ||
327
+ "__env__" in globalThis
327
328
  ) {
328
329
  return SERVERLESS_PROCESSING_STUCK_AFTER_MS;
329
330
  }
@@ -77,12 +77,14 @@ function parseTimeoutMs(value: string | undefined): number | undefined {
77
77
  function isServerlessHost(): boolean {
78
78
  // Detection mirrors db/migrations.ts:297-301. On Cloudflare Workers/Pages,
79
79
  // `process.env` is shimmed and CF_PAGES isn't reliably populated at runtime —
80
- // the canonical signal is the `__cf_env` global injected by workerd.
80
+ // the canonical signal is the `__cf_env`/`__env__` global injected by the
81
+ // Cloudflare runtime adapter.
81
82
  return (
82
83
  !!process.env.NETLIFY ||
83
84
  !!process.env.AWS_LAMBDA_FUNCTION_NAME ||
84
85
  !!process.env.VERCEL ||
85
- "__cf_env" in globalThis
86
+ "__cf_env" in globalThis ||
87
+ "__env__" in globalThis
86
88
  );
87
89
  }
88
90