@agent-native/core 0.145.8 → 0.146.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 (155) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +41 -11
  3. package/corpus/templates/analytics/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  4. package/corpus/templates/analytics/actions/compose-dashboard.ts +2 -27
  5. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +18 -3
  6. package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +75 -24
  7. package/corpus/templates/analytics/actions/mutate-dashboard.ts +16 -24
  8. package/corpus/templates/analytics/actions/rename-dashboard.ts +2 -24
  9. package/corpus/templates/analytics/actions/reorder-dashboard-panels.ts +2 -24
  10. package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +2 -25
  11. package/corpus/templates/analytics/actions/update-dashboard.ts +4 -32
  12. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +1 -40
  13. package/corpus/templates/analytics/changelog/2026-08-07-analytics-migrations-now-use-bounded-pressure-aware-backgrou.md +6 -0
  14. package/corpus/templates/analytics/changelog/2026-08-07-language-selection-now-lives-in-settings-instead-of-the-comm.md +6 -0
  15. package/corpus/templates/analytics/changelog/2026-08-07-template-activity-charts-now-show-only-core-agent-native-app.md +6 -0
  16. package/corpus/templates/analytics/netlify.toml +7 -0
  17. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +110 -0
  18. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +450 -0
  19. package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +2 -2
  20. package/corpus/templates/analytics/server/lib/analytics-rollup-lock.ts +4 -5
  21. package/corpus/templates/analytics/server/lib/dashboard-collab-sync.ts +77 -0
  22. package/corpus/templates/analytics/server/lib/demo-dashboards.ts +2 -23
  23. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +10 -4
  24. package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +4 -11
  25. package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +2 -5
  26. package/corpus/templates/analytics/server/plugins/db.ts +42 -0
  27. package/corpus/templates/analytics/server/routes/api/analytics-bigquery/backfill.post.ts +55 -0
  28. package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +18 -4
  29. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +41 -11
  30. package/corpus/templates/assets/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  31. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +41 -11
  32. package/corpus/templates/brain/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  33. package/corpus/templates/brain/server/lib/connectors.ts +38 -14
  34. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +41 -11
  35. package/corpus/templates/calendar/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  36. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +41 -11
  37. package/corpus/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  38. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +41 -11
  39. package/corpus/templates/clips/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  40. package/corpus/templates/clips/app/components/player/video-player.tsx +1 -1
  41. package/corpus/templates/clips/changelog/2026-08-07-fixed-trimmed-clips-durations-so-the-main-player-display-mat.md +6 -0
  42. package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +1 -1
  43. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  44. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +41 -11
  45. package/corpus/templates/content/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  46. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +41 -11
  47. package/corpus/templates/crm/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  48. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +41 -11
  49. package/corpus/templates/design/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  50. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +41 -11
  51. package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  52. package/corpus/templates/dispatch/changelog/2026-08-07-language-selection-now-lives-in-settings-instead-of-the-disp.md +6 -0
  53. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +41 -11
  54. package/corpus/templates/factory/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  55. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +59 -22
  56. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +41 -11
  57. package/corpus/templates/forms/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  58. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +41 -11
  59. package/corpus/templates/macros/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  60. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +41 -11
  61. package/corpus/templates/mail/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  62. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +41 -11
  63. package/corpus/templates/plan/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  64. package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +10 -1
  65. package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +10 -3
  66. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +41 -11
  67. package/corpus/templates/slides/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  68. package/corpus/templates/slides/actions/generate-image-api.ts +123 -0
  69. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +10 -2
  70. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +13 -0
  71. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -0
  72. package/corpus/templates/slides/app/components/editor/ImageGenPanel.tsx +1 -1
  73. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +5 -0
  74. package/corpus/templates/slides/app/components/presentation/PresenterView.tsx +5 -0
  75. package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -0
  76. package/corpus/templates/slides/app/pages/Presentation.tsx +3 -0
  77. package/corpus/templates/slides/app/pages/SharedPresentation.tsx +5 -0
  78. package/corpus/templates/slides/app/routes/slide.tsx +10 -5
  79. package/corpus/templates/slides/changelog/2026-08-07-deleting-a-deck-no-longer-freezes-the-slides-browser.md +6 -0
  80. package/corpus/templates/slides/changelog/2026-08-07-slide-previews-and-present-now-keep-deck-colors-consistent.md +6 -0
  81. package/corpus/templates/slides/changelog/2026-08-07-slides-verify-generated-images-are-persisted-on-the-target-s.md +6 -0
  82. package/corpus/templates/slides/server/db/schema.ts +1 -0
  83. package/corpus/templates/slides/server/handlers/share.ts +51 -3
  84. package/corpus/templates/slides/server/lib/slide-image-insertion.ts +141 -0
  85. package/corpus/templates/slides/server/plugins/agent-chat.ts +2 -0
  86. package/corpus/templates/slides/server/plugins/db.ts +5 -0
  87. package/corpus/templates/slides/shared/api.ts +2 -0
  88. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +41 -11
  89. package/corpus/templates/tasks/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  90. package/dist/cli/code-agent-executor.js +8 -3
  91. package/dist/client/AgentPanel.d.ts +10 -4
  92. package/dist/client/AgentPanel.js +29 -82
  93. package/dist/client/ConnectBuilderCard.d.ts +7 -7
  94. package/dist/client/ConnectBuilderCard.js +45 -20
  95. package/dist/collab/awareness.d.ts +2 -2
  96. package/dist/config.d.ts +9 -2
  97. package/dist/config.js +38 -2
  98. package/dist/db/client.d.ts +13 -1
  99. package/dist/db/client.js +76 -2
  100. package/dist/db/create-get-db.js +2 -2
  101. package/dist/db/migrations.js +22 -4
  102. package/dist/deploy/build.d.ts +1 -0
  103. package/dist/deploy/build.js +32 -23
  104. package/dist/notifications/routes.d.ts +3 -3
  105. package/dist/progress/routes.d.ts +1 -1
  106. package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
  107. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  108. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  109. package/dist/scripts/docs/search.js +3 -2
  110. package/dist/server/agent-chat/prompt-resources.js +4 -3
  111. package/dist/server/agents-bundle.d.ts +20 -3
  112. package/dist/server/agents-bundle.js +57 -30
  113. package/dist/server/better-auth-instance.js +2 -2
  114. package/dist/server/builder-browser.d.ts +34 -0
  115. package/dist/server/builder-browser.js +179 -0
  116. package/dist/server/core-routes-plugin.d.ts +5 -0
  117. package/dist/server/core-routes-plugin.js +24 -8
  118. package/dist/server/index.d.ts +1 -1
  119. package/dist/server/index.js +1 -1
  120. package/dist/shared/cache-control.d.ts +2 -2
  121. package/dist/shared/cache-control.js +2 -2
  122. package/dist/shared/runtime-config.js +1 -1
  123. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +41 -11
  124. package/dist/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  125. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +41 -11
  126. package/dist/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  127. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +41 -11
  128. package/dist/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  129. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +41 -11
  130. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  131. package/dist/vite/agent-native-config-loader.d.ts +10 -0
  132. package/dist/vite/agent-native-config-loader.js +56 -0
  133. package/dist/vite/agents-bundle-plugin.d.ts +5 -1
  134. package/dist/vite/agents-bundle-plugin.js +29 -6
  135. package/dist/vite/client.d.ts +4 -2
  136. package/dist/vite/client.js +17 -50
  137. package/dist/vite/index.d.ts +2 -2
  138. package/dist/vite/index.js +1 -1
  139. package/docs/content/agent-native-config.mdx +43 -17
  140. package/docs/content/drop-in-agent.mdx +3 -3
  141. package/docs/content/environment-variables.mdx +1 -1
  142. package/docs/content/getting-started-actions.mdx +1 -2
  143. package/docs/content/getting-started-pages.mdx +1 -2
  144. package/docs/content/getting-started.mdx +0 -4
  145. package/docs/content/what-is-agent-native.mdx +4 -4
  146. package/package.json +1 -1
  147. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +41 -11
  148. package/src/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  149. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +41 -11
  150. package/src/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  151. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +41 -11
  152. package/src/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  153. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +41 -11
  154. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
  155. package/docs/content/getting-started-database.mdx +0 -173
@@ -1,27 +1,27 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { IconExternalLink, IconLoader2 } from "@tabler/icons-react";
2
+ import { IconCheck, IconCode, IconCopy, IconExternalLink, IconLoader2, } from "@tabler/icons-react";
3
3
  import { useCallback, useEffect, useRef, useState } from "react";
4
4
  import { withBuilderUtmTrackingParams } from "../shared/builder-link-tracking.js";
5
5
  import { agentNativePath } from "./api-path.js";
6
6
  import { BuilderBMark } from "./builder-mark.js";
7
+ import { writeClipboardText } from "./clipboard.js";
7
8
  import { getCallbackOrigin } from "./frame.js";
8
9
  import { useBuilderConnectFlow } from "./settings/useBuilderStatus.js";
9
10
  import { cn } from "./utils.js";
10
11
  const DESKTOP_DOWNLOAD_URL = "https://www.agent-native.com/download";
11
12
  const CODE_CHANGE_FALLBACK_DETAIL = "Edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way you like.";
12
13
  const CODE_CHANGE_FALLBACK_TEXT = `This requires a code change. ${CODE_CHANGE_FALLBACK_DETAIL}`;
13
- function isLocalBrowserOutsideDesktop() {
14
- if (typeof window === "undefined" || typeof navigator === "undefined") {
14
+ function isLocalDevelopment() {
15
+ if (typeof window === "undefined") {
15
16
  return false;
16
17
  }
17
18
  const hostname = window.location.hostname;
18
- const local = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
19
- return local && !/AgentNativeDesktop/i.test(navigator.userAgent || "");
19
+ return (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1");
20
20
  }
21
21
  /**
22
- * Rich inline card rendered for the `connect-builder` tool call. Shows a
23
- * prominent Connect button that opens the Builder CLI auth flow and polls
24
- * /_agent-native/builder/status until credentials land.
22
+ * Rich inline card rendered for the `connect-builder` tool call. Shows the
23
+ * Builder handoff when available, or routes local development requests to the
24
+ * external coding agent.
25
25
  */
26
26
  export function ConnectBuilderCard({ configured: initialConfigured, builderEnabled: initialBuilderEnabled = true, connectUrl: initialConnectUrl, orgName: initialOrgName, prompt = "", }) {
27
27
  // The connect-poll state machine is shared — the tool-call result is
@@ -46,8 +46,11 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
46
46
  const [sending, setSending] = useState(false);
47
47
  const [runResult, setRunResult] = useState(null);
48
48
  const [sendErr, setSendErr] = useState(null);
49
- const [localBrowser, setLocalBrowser] = useState(false);
49
+ const [copyErr, setCopyErr] = useState(null);
50
+ const [promptCopied, setPromptCopied] = useState(false);
51
+ const [localDevelopment] = useState(() => isLocalDevelopment());
50
52
  const mountedRef = useRef(true);
53
+ const copyResetRef = useRef(null);
51
54
  // Tracks whether the user clicked "Connect Builder" *this session*. When
52
55
  // the connect-then-poll round-trip lands `configured=true`, we use this
53
56
  // flag to decide whether to retry the user's pending prompt automatically
@@ -58,9 +61,10 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
58
61
  const wasConnectingRef = useRef(false);
59
62
  useEffect(() => {
60
63
  mountedRef.current = true;
61
- setLocalBrowser(isLocalBrowserOutsideDesktop());
62
64
  return () => {
63
65
  mountedRef.current = false;
66
+ if (copyResetRef.current)
67
+ clearTimeout(copyResetRef.current);
64
68
  };
65
69
  }, []);
66
70
  const handleSend = useCallback(async () => {
@@ -127,8 +131,27 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
127
131
  setJoiningWaitlist(false);
128
132
  }
129
133
  }, [orgName, prompt]);
130
- // Combine connect-flow errors, send errors, and waitlist errors.
131
- const err = sendErr ?? waitlistErr ?? flow.error;
134
+ const handleCopyPrompt = useCallback(async () => {
135
+ if (!prompt.trim())
136
+ return;
137
+ setCopyErr(null);
138
+ const copied = await writeClipboardText(prompt);
139
+ if (!mountedRef.current)
140
+ return;
141
+ if (!copied) {
142
+ setCopyErr("Couldn't copy the prompt");
143
+ return;
144
+ }
145
+ setPromptCopied(true);
146
+ if (copyResetRef.current)
147
+ clearTimeout(copyResetRef.current);
148
+ copyResetRef.current = setTimeout(() => {
149
+ if (mountedRef.current)
150
+ setPromptCopied(false);
151
+ }, 1600);
152
+ }, [prompt]);
153
+ // Combine connect-flow errors, send errors, waitlist errors, and copy errors.
154
+ const err = sendErr ?? waitlistErr ?? copyErr ?? flow.error;
132
155
  const hasPrompt = prompt.trim().length > 0;
133
156
  const canSend = configured && builderEnabled && hasPrompt;
134
157
  // Auto-send the user's pending prompt the moment connecting finishes
@@ -149,7 +172,8 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
149
172
  }, [flow.connecting, canSend, sending, runResult, sendErr, handleSend]);
150
173
  // Branch creation is gated by a server-side project id, which may come
151
174
  // from deployment config or org-scoped secrets.
152
- const showWaitlist = !builderEnabled && hasPrompt;
175
+ const showExternalAgentHandoff = localDevelopment && !builderEnabled && hasPrompt;
176
+ const showWaitlist = !localDevelopment && !builderEnabled && hasPrompt;
153
177
  // Title + subtitle depend on which mode we're in. We compute them up front
154
178
  // so the render tree below stays flat.
155
179
  const connectedCapabilityText = builderEnabled
@@ -161,13 +185,14 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
161
185
  title = "Builder is working on it";
162
186
  subtitle = (_jsxs(_Fragment, { children: ["Working on branch", " ", _jsx("span", { className: "font-mono text-foreground", children: runResult.branchName }), ". Click through to watch progress in the Visual Editor."] }));
163
187
  }
188
+ else if (showExternalAgentHandoff) {
189
+ title = "This requires a code change";
190
+ subtitle =
191
+ "Open your coding agent in this project, then paste this request.";
192
+ }
164
193
  else if (showWaitlist) {
165
194
  title = "This requires a code change";
166
- subtitle = waitlistJoined ? (_jsxs(_Fragment, { children: ["You're on the waitlist. ", CODE_CHANGE_FALLBACK_DETAIL, " ", localBrowser
167
- ? "Since this project is already running locally, open it in the desktop app for local coding tools or keep editing from your clone."
168
- : "You can still clone the project locally and use the desktop app for code changes."] })) : (_jsxs(_Fragment, { children: [CODE_CHANGE_FALLBACK_DETAIL, " ", localBrowser
169
- ? "Since this project is already running locally, open it in the desktop app for local coding tools or keep editing from your clone."
170
- : "You can still clone the project locally and use the desktop app for code changes."] }));
195
+ subtitle = waitlistJoined ? (_jsxs(_Fragment, { children: ["You're on the waitlist. ", CODE_CHANGE_FALLBACK_DETAIL, " You can still clone the project locally and use the desktop app for code changes."] })) : (_jsxs(_Fragment, { children: [CODE_CHANGE_FALLBACK_DETAIL, " You can still clone the project locally and use the desktop app for code changes."] }));
171
196
  }
172
197
  else if (canSend) {
173
198
  title = "Send this to Builder";
@@ -181,8 +206,8 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
181
206
  title = "Connect Builder.io";
182
207
  subtitle = _jsx(_Fragment, { children: "Builder.io's free tier includes AI credits." });
183
208
  }
184
- return (_jsx("div", { className: cn("my-2 rounded-lg border border-border overflow-hidden"), children: _jsxs("div", { className: "flex items-start gap-3 px-4 py-3.5 bg-gradient-to-br from-teal-500/5 via-transparent to-transparent", children: [_jsx("div", { className: cn("flex h-9 w-9 shrink-0 items-center justify-center rounded-lg", "bg-foreground text-background"), children: runResult ? (_jsx(IconLoader2, { className: "h-5 w-5 animate-spin" })) : (_jsx(BuilderBMark, { className: "h-5 w-5" })) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "flex items-center gap-2 flex-wrap", children: _jsx("span", { className: "text-sm font-semibold text-foreground", children: title }) }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground leading-relaxed", children: subtitle }), showWaitlist && (_jsxs("a", { href: DESKTOP_DOWNLOAD_URL, target: "_blank", rel: "noopener noreferrer", className: "mt-2 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground no-underline hover:text-foreground", children: ["Download desktop app", _jsx(IconExternalLink, { className: "h-3 w-3" })] })), err && _jsx("div", { className: "mt-2 text-xs text-destructive", children: err }), _jsx("div", { className: "mt-3", children: runResult ? (_jsxs("a", { href: withBuilderUtmTrackingParams(runResult.url, {
209
+ return (_jsx("div", { className: cn("my-2 rounded-lg border border-border overflow-hidden"), children: _jsxs("div", { className: "flex items-start gap-3 px-4 py-3.5 bg-gradient-to-br from-teal-500/5 via-transparent to-transparent", children: [_jsx("div", { className: cn("flex h-9 w-9 shrink-0 items-center justify-center rounded-lg", "bg-foreground text-background"), children: runResult ? (_jsx(IconLoader2, { className: "h-5 w-5 animate-spin" })) : showExternalAgentHandoff ? (_jsx(IconCode, { className: "h-5 w-5" })) : (_jsx(BuilderBMark, { className: "h-5 w-5" })) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "flex items-center gap-2 flex-wrap", children: _jsx("span", { className: "text-sm font-semibold text-foreground", children: title }) }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground leading-relaxed", children: subtitle }), showWaitlist && (_jsxs("a", { href: DESKTOP_DOWNLOAD_URL, target: "_blank", rel: "noopener noreferrer", className: "mt-2 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground no-underline hover:text-foreground", children: ["Download desktop app", _jsx(IconExternalLink, { className: "h-3 w-3" })] })), err && _jsx("div", { className: "mt-2 text-xs text-destructive", children: err }), _jsx("div", { className: "mt-3", children: runResult ? (_jsxs("a", { href: withBuilderUtmTrackingParams(runResult.url, {
185
210
  campaign: "product",
186
211
  content: "connect_builder_card_branch",
187
- }), target: "_blank", rel: "noopener noreferrer", className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: ["Open branch in Builder", _jsx(IconExternalLink, { className: "h-3.5 w-3.5" })] })) : canSend ? (_jsx("button", { type: "button", onClick: handleSend, disabled: sending, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", sending && "opacity-70 cursor-wait"), children: sending ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Sending to Builder\u2026"] })) : (_jsx(_Fragment, { children: "Send to Builder" })) })) : showWaitlist && !waitlistJoined ? (_jsx("button", { type: "button", onClick: handleJoinWaitlist, disabled: joiningWaitlist, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", joiningWaitlist && "opacity-70 cursor-wait"), children: joiningWaitlist ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Joining\u2026"] })) : (_jsx(_Fragment, { children: "Join the waitlist" })) })) : !configured ? (_jsx("button", { type: "button", onClick: () => flow.start(), disabled: connecting, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", connecting && "opacity-70 cursor-wait"), children: connecting ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Waiting for Builder\u2026"] })) : ("Connect Builder") })) : null })] })] }) }));
212
+ }), target: "_blank", rel: "noopener noreferrer", className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: ["Open branch in Builder", _jsx(IconExternalLink, { className: "h-3.5 w-3.5" })] })) : canSend ? (_jsx("button", { type: "button", onClick: handleSend, disabled: sending, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", sending && "opacity-70 cursor-wait"), children: sending ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Sending to Builder\u2026"] })) : (_jsx(_Fragment, { children: "Send to Builder" })) })) : showExternalAgentHandoff ? (_jsx("button", { type: "button", onClick: () => void handleCopyPrompt(), className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: promptCopied ? (_jsxs(_Fragment, { children: [_jsx(IconCheck, { className: "h-3.5 w-3.5" }), "Prompt copied"] })) : (_jsxs(_Fragment, { children: [_jsx(IconCopy, { className: "h-3.5 w-3.5" }), "Copy prompt"] })) })) : showWaitlist && !waitlistJoined ? (_jsx("button", { type: "button", onClick: handleJoinWaitlist, disabled: joiningWaitlist, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", joiningWaitlist && "opacity-70 cursor-wait"), children: joiningWaitlist ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Joining\u2026"] })) : (_jsx(_Fragment, { children: "Join the waitlist" })) })) : !configured ? (_jsx("button", { type: "button", onClick: () => flow.start(), disabled: connecting, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", connecting && "opacity-70 cursor-wait"), children: connecting ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Waiting for Builder\u2026"] })) : ("Connect Builder") })) : null })] })] }) }));
188
213
  }
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
- error?: undefined;
66
65
  states: {
67
66
  clientId: number;
68
67
  state: string;
69
68
  }[];
69
+ error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
- error?: undefined;
81
80
  users: {
82
81
  clientId: number;
83
82
  lastSeen: number;
84
83
  }[];
84
+ error?: undefined;
85
85
  }>>;
package/dist/config.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Public, non-secret configuration for an Agent-Native app.
3
3
  *
4
4
  * This module is intentionally free of Node and framework imports so it can be
5
- * used from a typed `agent-native.ts` file and from browser code after Vite
5
+ * used from a typed `agent-native.config.ts` file and from browser code after Vite
6
6
  * serializes the resolved config into the client bundle.
7
7
  */
8
8
  export declare const AGENT_NATIVE_CONFIG_VERSION: 1;
@@ -42,11 +42,18 @@ export interface AgentNativeDiagnosticsConfig {
42
42
  /** Fail a production Vite build when runtime configuration has issues. */
43
43
  failOnBuild?: boolean;
44
44
  }
45
+ export interface AgentNativeInstructionsConfig {
46
+ /** Relative Markdown file loaded by the in-app runtime agent. */
47
+ runtime?: string;
48
+ /** Relative Markdown file loaded by development/coding agents. */
49
+ development?: string;
50
+ }
45
51
  export interface AgentNativeConfig {
46
52
  version?: typeof AGENT_NATIVE_CONFIG_VERSION;
47
53
  onboarding?: AgentNativeOnboardingConfig;
48
54
  runtime?: AgentNativeRuntimeConfig;
49
55
  diagnostics?: AgentNativeDiagnosticsConfig;
56
+ instructions?: AgentNativeInstructionsConfig;
50
57
  }
51
58
  export interface AgentNativeConfigContext {
52
59
  command: "serve" | "build";
@@ -57,7 +64,7 @@ export interface AgentNativeConfigContext {
57
64
  export type AgentNativeConfigFactory = (context: AgentNativeConfigContext) => AgentNativeConfig;
58
65
  export type AgentNativeConfigInput = AgentNativeConfig | AgentNativeConfigFactory;
59
66
  /**
60
- * Type-safe authoring helper for `agent-native.ts`.
67
+ * Type-safe authoring helper for `agent-native.config.ts`.
61
68
  *
62
69
  * Like Next's typed config file, this is deliberately identity-like: the
63
70
  * framework evaluates the exported object or factory in the Vite config
package/dist/config.js CHANGED
@@ -2,12 +2,12 @@
2
2
  * Public, non-secret configuration for an Agent-Native app.
3
3
  *
4
4
  * This module is intentionally free of Node and framework imports so it can be
5
- * used from a typed `agent-native.ts` file and from browser code after Vite
5
+ * used from a typed `agent-native.config.ts` file and from browser code after Vite
6
6
  * serializes the resolved config into the client bundle.
7
7
  */
8
8
  export const AGENT_NATIVE_CONFIG_VERSION = 1;
9
9
  /**
10
- * Type-safe authoring helper for `agent-native.ts`.
10
+ * Type-safe authoring helper for `agent-native.config.ts`.
11
11
  *
12
12
  * Like Next's typed config file, this is deliberately identity-like: the
13
13
  * framework evaluates the exported object or factory in the Vite config
@@ -27,6 +27,7 @@ export function normalizeAgentNativeConfig(input, source = "agent-native config"
27
27
  const onboardingValue = input.onboarding;
28
28
  const runtimeValue = input.runtime;
29
29
  const diagnosticsValue = input.diagnostics;
30
+ const instructionsValue = input.instructions;
30
31
  const normalized = {
31
32
  ...(input.version === undefined
32
33
  ? {}
@@ -45,6 +46,9 @@ export function normalizeAgentNativeConfig(input, source = "agent-native config"
45
46
  if (diagnosticsValue !== undefined) {
46
47
  normalized.diagnostics = normalizeDiagnosticsConfig(diagnosticsValue, `${source}.diagnostics`);
47
48
  }
49
+ if (instructionsValue !== undefined) {
50
+ normalized.instructions = normalizeInstructionsConfig(instructionsValue, `${source}.instructions`);
51
+ }
48
52
  return normalized;
49
53
  }
50
54
  export function mergeAgentNativeConfigs(base, override) {
@@ -91,6 +95,12 @@ export function mergeAgentNativeConfigs(base, override) {
91
95
  ...override.diagnostics,
92
96
  }
93
97
  : undefined,
98
+ instructions: base.instructions || override.instructions
99
+ ? {
100
+ ...base.instructions,
101
+ ...override.instructions,
102
+ }
103
+ : undefined,
94
104
  };
95
105
  }
96
106
  export function resolveAgentNativeConfig(input, context) {
@@ -175,6 +185,32 @@ function normalizeDiagnosticsConfig(value, source) {
175
185
  ? {}
176
186
  : { failOnBuild: value.failOnBuild };
177
187
  }
188
+ function normalizeInstructionsConfig(value, source) {
189
+ if (!isRecord(value)) {
190
+ throw new Error(`${source} must be an object`);
191
+ }
192
+ const result = {};
193
+ for (const audience of ["runtime", "development"]) {
194
+ const pathValue = value[audience];
195
+ if (pathValue === undefined)
196
+ continue;
197
+ if (typeof pathValue !== "string") {
198
+ throw new Error(`${source}.${audience} must be a relative file path`);
199
+ }
200
+ result[audience] = normalizeRelativeFilePath(pathValue, `${source}.${audience}`);
201
+ }
202
+ return result;
203
+ }
204
+ function normalizeRelativeFilePath(value, source) {
205
+ const normalized = value.trim().replaceAll("\\", "/");
206
+ if (!normalized ||
207
+ normalized.startsWith("/") ||
208
+ /^[A-Za-z]:\//.test(normalized) ||
209
+ normalized.split("/").some((segment) => segment === "..")) {
210
+ throw new Error(`${source} must be a non-empty relative file path inside the app root`);
211
+ }
212
+ return normalized;
213
+ }
178
214
  function normalizeRequiredEnvKeys(value, source) {
179
215
  if (value === undefined)
180
216
  return undefined;
@@ -227,7 +227,19 @@ export declare function isBackgroundFunctionPoolContext(): boolean;
227
227
  * actual context.
228
228
  */
229
229
  export declare function describeDbError(err: unknown): string;
230
- export declare function attachNeonPoolErrorLogger(pool: unknown, label?: string): void;
230
+ /**
231
+ * Thrown INSTEAD of attempting, while an endpoint is in cooldown.
232
+ *
233
+ * The refused attempt's error is described in the message and deliberately NOT
234
+ * attached as `cause`: isConnectionError() walks `cause.code`/`cause.message`,
235
+ * so a cause would reclassify this as retryable and retryOnConnectionError
236
+ * would drive the storm straight back.
237
+ */
238
+ export declare class DbConnectCooldownError extends Error {
239
+ code: string;
240
+ constructor(remainingMs: number, refusedBy: string);
241
+ }
242
+ export declare function guardNeonPool(pool: unknown, url: string, label?: string): void;
231
243
  interface ClosablePool {
232
244
  end(): Promise<unknown>;
233
245
  }
package/dist/db/client.js CHANGED
@@ -613,6 +613,9 @@ export function isTransientDatabaseError(err) {
613
613
  const error = err;
614
614
  const code = String(error?.code ?? error?.cause?.code ?? "");
615
615
  if (code === "ECHECKOUTTIMEOUT" ||
616
+ // Shed load during a connect cooldown is transient by construction — the
617
+ // endpoint refused, we chose not to re-ask yet. Surfaces as 503, not 500.
618
+ code === "DB_CONNECT_COOLDOWN" ||
616
619
  code === "EMAXCONN" ||
617
620
  code === "53300" ||
618
621
  code === "57014" ||
@@ -960,7 +963,77 @@ export function describeDbError(err) {
960
963
  }
961
964
  return String(err);
962
965
  }
963
- export function attachNeonPoolErrorLogger(pool, label = "db/neon") {
966
+ /**
967
+ * How long one endpoint stops ATTEMPTING to connect after an attempt failed.
968
+ * Jittered so concurrent function instances do not re-probe in lockstep.
969
+ */
970
+ function connectCooldownMs() {
971
+ const raw = Number(process.env.DB_CONNECT_COOLDOWN_MS);
972
+ const base = Number.isFinite(raw) && raw > 0 ? raw : 2_000;
973
+ return Math.round(base * (0.5 + Math.random()));
974
+ }
975
+ /**
976
+ * Thrown INSTEAD of attempting, while an endpoint is in cooldown.
977
+ *
978
+ * The refused attempt's error is described in the message and deliberately NOT
979
+ * attached as `cause`: isConnectionError() walks `cause.code`/`cause.message`,
980
+ * so a cause would reclassify this as retryable and retryOnConnectionError
981
+ * would drive the storm straight back.
982
+ */
983
+ export class DbConnectCooldownError extends Error {
984
+ code = "DB_CONNECT_COOLDOWN";
985
+ constructor(remainingMs, refusedBy) {
986
+ super(`Database is refusing connection attempts; not attempting again for ${remainingMs}ms. Last failure: ${refusedBy}`);
987
+ this.name = "DbConnectCooldownError";
988
+ }
989
+ }
990
+ const connectCooldowns = new Map();
991
+ /**
992
+ * Neon rejects a connection ATTEMPT, not a connection: "Failed to acquire
993
+ * permit to connect to the database. Too many database connection attempts are
994
+ * currently ongoing." A failed acquire leaves the pool with zero idle clients,
995
+ * so the next execute() calls connect() again — and retryOnConnectionError
996
+ * backs off only 100ms. The process answers a refusal by manufacturing the next
997
+ * attempt, which is what keeps the refusal true. Production sat in this loop
998
+ * until the compute was restarted by hand.
999
+ *
1000
+ * Hold a short cooldown per endpoint so a refused attempt cannot produce the
1001
+ * next one. Checking out an ALREADY-IDLE client is not an attempt and passes
1002
+ * through, so a cooldown degrades throughput instead of taking the process
1003
+ * offline.
1004
+ */
1005
+ function gateNeonConnect(pool, url, label) {
1006
+ const connect = pool.connect;
1007
+ if (typeof connect !== "function")
1008
+ return;
1009
+ const gated = function gatedConnect(...args) {
1010
+ const idle = typeof pool.idleCount === "number" ? pool.idleCount : 0;
1011
+ const gate = connectCooldowns.get(url);
1012
+ if (idle === 0 && gate && Date.now() < gate.until) {
1013
+ return Promise.reject(new DbConnectCooldownError(gate.until - Date.now(), gate.refusedBy));
1014
+ }
1015
+ return Promise.resolve(connect.apply(this, args)).then((client) => {
1016
+ connectCooldowns.delete(url);
1017
+ return client;
1018
+ }, (err) => {
1019
+ const refusedBy = describeDbError(err);
1020
+ const ms = connectCooldownMs();
1021
+ // One line per cooldown window, not per shed request.
1022
+ if (!gate || Date.now() >= gate.until) {
1023
+ console.warn(`[${label}] connection attempt refused; pausing attempts ${ms}ms:`, refusedBy);
1024
+ }
1025
+ connectCooldowns.set(url, { until: Date.now() + ms, refusedBy });
1026
+ throw err;
1027
+ });
1028
+ };
1029
+ // Carry the wrapped function's own properties onto the wrapper. A pool's
1030
+ // `connect` may be instrumented — metrics, tracing, a test spy — and
1031
+ // replacing it with a bare closure would silently drop that instrumentation
1032
+ // along with any assertions built on it.
1033
+ Object.assign(gated, connect);
1034
+ pool.connect = gated;
1035
+ }
1036
+ export function guardNeonPool(pool, url, label = "db/neon") {
964
1037
  if (!pool || typeof pool !== "object")
965
1038
  return;
966
1039
  if (loggedNeonPools.has(pool))
@@ -969,6 +1042,7 @@ export function attachNeonPoolErrorLogger(pool, label = "db/neon") {
969
1042
  if (typeof withEvents.on !== "function")
970
1043
  return;
971
1044
  loggedNeonPools.add(pool);
1045
+ gateNeonConnect(pool, url, label);
972
1046
  withEvents.on("error", (err) => {
973
1047
  console.warn(`[${label}] pool error (will reconnect on next query):`, describeDbError(err));
974
1048
  });
@@ -1177,7 +1251,7 @@ async function createDbExecInternal(config = {}, trackSingletonResources = false
1177
1251
  const pool = trackSingletonResources
1178
1252
  ? sharedDbPool("neon", url, makePool)
1179
1253
  : makePool();
1180
- attachNeonPoolErrorLogger(pool);
1254
+ guardNeonPool(pool, url);
1181
1255
  const httpSql = bgHttp ? neon(url, { fullResults: true }) : null;
1182
1256
  async function queryNeonClient(client, sql, timeoutOverrideMs) {
1183
1257
  const { rawSql, args } = sqlAndArgs(sql);
@@ -1,6 +1,6 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { drizzle as drizzleD1 } from "drizzle-orm/d1";
3
- import { getDialect, getCloudflareD1Binding, getDatabaseUrl, getDatabaseAuthToken, isLocalSqliteUrl, isPgliteUrl, isConnectionError, getPgliteClient, loadPgliteDrizzle, prepareLocalSqliteUrl, sqliteFilenameFromUrl, pgPoolOptions, neonPoolOptions, attachNeonPoolErrorLogger, withDbTimeout, retryOnConnectionError, dbOpTimeoutMs, sharedDbPool, onSharedDbPoolsClosed, onSharedDbPoolReplaced, } from "./client.js";
3
+ import { getDialect, getCloudflareD1Binding, getDatabaseUrl, getDatabaseAuthToken, isLocalSqliteUrl, isPgliteUrl, isConnectionError, getPgliteClient, loadPgliteDrizzle, prepareLocalSqliteUrl, sqliteFilenameFromUrl, pgPoolOptions, neonPoolOptions, guardNeonPool, withDbTimeout, retryOnConnectionError, dbOpTimeoutMs, sharedDbPool, onSharedDbPoolsClosed, onSharedDbPoolReplaced, } from "./client.js";
4
4
  // Lazy driver loaders — cached promises so dynamic import only runs once.
5
5
  let _pgDrizzle;
6
6
  function getPgDrizzle() {
@@ -408,7 +408,7 @@ export function createGetDb(schema) {
408
408
  // schema module. See `sharedDbPool` in client.ts.
409
409
  resetOnPoolClose("neon", url);
410
410
  const rawPool = sharedDbPool("neon", url, () => new Pool({ connectionString: url, ...neonPoolOptions() }));
411
- attachNeonPoolErrorLogger(rawPool);
411
+ guardNeonPool(rawPool, url);
412
412
  // Wrap the pool with the resilience layer so Drizzle queries get the
413
413
  // same withDbTimeout + retryOnConnectionError protection as the raw
414
414
  // DbExec path in client.ts. Reads retry freely; writes only retry on
@@ -245,11 +245,28 @@ function isServerlessRequestRuntime() {
245
245
  process.env.VERCEL === "1");
246
246
  }
247
247
  /**
248
- * A runtime that is ALLOWED to migrate: scheduled jobs and durable background
249
- * workers, which are off the request path and may take as long as they need.
248
+ * Whether this app migrates at RELEASE time, so skipping on the request path
249
+ * is safe.
250
250
  *
251
- * Set by whichever runtime claims migration duty. Analytics sets its own
252
- * equivalent flag before running the scheduled rollup.
251
+ * Without this, "skip in serverless" silently means "never migrate" for any app
252
+ * that has no release entrypoint — and at the time this was written exactly one
253
+ * of seventeen templates had one. Existing tables keep working, so nothing
254
+ * fails; a newly added migration just never applies, and a fresh deploy comes
255
+ * up with missing tables. That is a skip indistinguishable from success, which
256
+ * is the failure mode this repo bans.
257
+ *
258
+ * So the request-path skip is opt-in per app. An app sets this once it owns a
259
+ * release migration step (see templates/analytics/scripts/migrate-production.ts
260
+ * and the `migrate:production` build step in its netlify.toml).
261
+ */
262
+ function appMigratesAtRelease() {
263
+ const raw = process.env.AGENT_NATIVE_RELEASE_MIGRATIONS?.trim();
264
+ return !!raw && ["1", "true", "yes", "on"].includes(raw.toLowerCase());
265
+ }
266
+ /**
267
+ * A runtime that is ALLOWED to migrate: release scripts, scheduled jobs, and
268
+ * durable background workers, which are off the request path and may take as
269
+ * long as they need. Claimed with {@link withMigrationRuntime}.
253
270
  */
254
271
  function isMigrationAuthorizedRuntime() {
255
272
  return (globalThis.__AGENT_NATIVE_MIGRATION_RUNTIME__ === true);
@@ -322,6 +339,7 @@ export function runMigrations(migrations, options) {
322
339
  // the explicit opt-in for a caller that genuinely cannot defer.
323
340
  if (options?.runInServerlessRequest !== true &&
324
341
  isServerlessRequestRuntime() &&
342
+ appMigratesAtRelease() &&
325
343
  !isMigrationAuthorizedRuntime()) {
326
344
  console.info(`[migrations] Skipping "${table}" migrations in a serverless request runtime`);
327
345
  return;
@@ -312,3 +312,4 @@ export declare function resolveNitroBundledYjsEntry(): string;
312
312
  * the small set above bundled to keep their package manifests traceable.
313
313
  */
314
314
  export declare function nitroNoExternalsForPreset(targetPreset: string): true | readonly string[];
315
+ export declare function resolveNitroBuildReplacements(env?: NodeJS.ProcessEnv): Record<string, string>;
@@ -23,10 +23,11 @@ import { INTEGRATION_RECOVERY_RUNTIME_MARKER, INTEGRATION_RETRY_SWEEP_PATH, INTE
23
23
  import { isValidCron } from "../jobs/cron.js";
24
24
  import { RECURRING_JOBS_SWEEP_PATH, RECURRING_JOBS_SWEEP_TOKEN_SUBJECT, } from "../jobs/scheduler-dispatch.js";
25
25
  import { normalizeAppBasePath } from "../server/app-base-path.js";
26
- import { DEFAULT_SPECULATION_RULES_PATH, resolveSsrCacheHeaders, } from "../shared/cache-control.js";
26
+ import { DEFAULT_SPECULATION_RULES_PATH, resolveSsrCacheHeaders, resolveSsrCacheKeyHeaders, } from "../shared/cache-control.js";
27
27
  import { mcpEmbedStaticAssetRouteRules } from "../shared/mcp-embed-headers.js";
28
28
  import { AGENT_NATIVE_SOCIAL_IMAGE_ALT, AGENT_NATIVE_SOCIAL_IMAGE_CACHE_BUSTER, AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT, AGENT_NATIVE_SOCIAL_IMAGE_PATH, AGENT_NATIVE_SOCIAL_IMAGE_TYPE, AGENT_NATIVE_SOCIAL_IMAGE_WIDTH, } from "../shared/social-meta.js";
29
29
  import { generateActionRegistryForProject } from "../vite/action-types-plugin.js";
30
+ import { createAgentNativeConfigContext, loadResolvedAgentNativeConfig, } from "../vite/agent-native-config-loader.js";
30
31
  import { cloneServerBundleForFunction, copyDir } from "./function-bundle.js";
31
32
  import { collectImmutableAssetPaths, IMMUTABLE_ASSET_CACHE_CONTROL, IMMUTABLE_ASSET_CACHE_HEADERS, prefixAssetPath, } from "./immutable-assets.js";
32
33
  import { discoverApiRoutes, discoverPlugins, discoverActionFiles, getMissingDefaultPlugins, DEFAULT_PLUGIN_REGISTRY, } from "./route-discovery.js";
@@ -651,6 +652,7 @@ export function generateWorkerEntry(routes, pluginPaths, defaultPluginStems = []
651
652
  // The worker ships as a static bundle with no access to runtime env, so the
652
653
  // deployment-wide SSR cache policy is baked in from this build's env.
653
654
  const ssrCacheHeaders = resolveSsrCacheHeaders();
655
+ const ssrCacheKeyHeaders = resolveSsrCacheKeyHeaders();
654
656
  const routeImports = [];
655
657
  const routeRegistrations = [];
656
658
  for (let i = 0; i < routes.length; i++) {
@@ -970,9 +972,8 @@ function injectHeadScript(html, script) {
970
972
  }
971
973
 
972
974
  // Resolved from AGENT_NATIVE_SSR_CACHE at build time.
973
- const SSR_CACHE_CONTROL = ${JSON.stringify(ssrCacheHeaders["cache-control"])};
974
- const SSR_CDN_CACHE_CONTROL = ${JSON.stringify(ssrCacheHeaders["cdn-cache-control"])};
975
- const SSR_NETLIFY_CDN_CACHE_CONTROL = ${JSON.stringify(ssrCacheHeaders["netlify-cdn-cache-control"])};
975
+ const SSR_CACHE_HEADERS = ${JSON.stringify(ssrCacheHeaders)};
976
+ const SSR_CACHE_KEY_HEADERS = ${JSON.stringify(ssrCacheKeyHeaders)};
976
977
  const DEFAULT_SPECULATION_RULES_PATH = ${JSON.stringify(DEFAULT_SPECULATION_RULES_PATH)};
977
978
  const IMMUTABLE_ASSET_CACHE_CONTROL = ${JSON.stringify(IMMUTABLE_ASSET_CACHE_CONTROL)};
978
979
  const IMMUTABLE_ASSET_PATHS = new Set(${JSON.stringify([...new Set(immutableAssetPaths)].sort())});
@@ -1056,13 +1057,12 @@ function applyDefaultSsrCacheHeader(headers, status, pathname) {
1056
1057
  else headers.delete("vary");
1057
1058
  }
1058
1059
 
1059
- headers.set("cache-control", SSR_CACHE_CONTROL);
1060
- headers.set("cdn-cache-control", SSR_CDN_CACHE_CONTROL);
1061
- // Netlify function responses are dynamic by default and can otherwise show
1062
- // Cache-Status fwd=bypass even with Cache-Control: public. Keep this
1063
- // Netlify-specific header so SSR HTML/.data are served from the shared
1064
- // durable CDN cache instead of stampeding origin — for every visitor.
1065
- headers.set("netlify-cdn-cache-control", SSR_NETLIFY_CDN_CACHE_CONTROL);
1060
+ for (const [name, value] of Object.entries(SSR_CACHE_HEADERS)) {
1061
+ headers.set(name, value);
1062
+ }
1063
+ for (const [name, value] of Object.entries(SSR_CACHE_KEY_HEADERS)) {
1064
+ headers.set(name, value);
1065
+ }
1066
1066
  }
1067
1067
 
1068
1068
  function applyDefaultSpeculationRulesHeader(headers, status, basePath) {
@@ -3463,6 +3463,22 @@ function createBrowserOnlyServerStubPlugin() {
3463
3463
  },
3464
3464
  };
3465
3465
  }
3466
+ export function resolveNitroBuildReplacements(env = process.env) {
3467
+ return {
3468
+ // Netlify exposes DEPLOY_ID only while building. Embed it into the Nitro
3469
+ // function so preview OAuth relays can target this immutable deployment
3470
+ // even though the value is unavailable in the function runtime.
3471
+ "process.env.AGENT_NATIVE_BUILD_ID": JSON.stringify(env.DEPLOY_ID?.trim() || env.AGENT_NATIVE_BUILD_ID?.trim() || ""),
3472
+ "process.env.AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID": JSON.stringify(env.GA_MEASUREMENT_ID?.trim() || ""),
3473
+ "process.env.AGENT_NATIVE_BUILD_GTM_CONTAINER_ID": JSON.stringify(env.GTM_CONTAINER_ID?.trim() || ""),
3474
+ // Netlify's netlify.toml environment is available while this deploy
3475
+ // build runs, but is not injected into the deployed Function. Nitro is
3476
+ // a separate server build, so embed release migration ownership here as
3477
+ // well as in the Vite server bundle.
3478
+ "process.env.AGENT_NATIVE_RELEASE_MIGRATIONS": JSON.stringify(env.AGENT_NATIVE_RELEASE_MIGRATIONS?.trim() || ""),
3479
+ "process.env.AGENT_NATIVE_BUILD_DEPLOY_CONTEXT": JSON.stringify(env.CONTEXT?.trim() || ""),
3480
+ };
3481
+ }
3466
3482
  async function buildWithNitro() {
3467
3483
  console.log(`[deploy] Building for preset "${preset}" via Nitro...`);
3468
3484
  const appBasePath = normalizeConfiguredAppBasePath();
@@ -3493,6 +3509,7 @@ async function buildWithNitro() {
3493
3509
  // own virtual module registration. Both paths reuse `readAgentsBundleFromFs`
3494
3510
  // from `server/agents-bundle.ts` to guarantee identical content.
3495
3511
  const { readAgentsBundleFromFs } = await import("../server/agents-bundle.js");
3512
+ const nitroAgentConfig = await loadResolvedAgentNativeConfig(cwd, createAgentNativeConfigContext("build", process.env.NODE_ENV === "development" ? "development" : "production"));
3496
3513
  // Resolve the workspace core (if present) up front so the bundle embeds
3497
3514
  // enterprise-wide AGENTS.md + skills alongside the template's.
3498
3515
  const nitroWorkspaceCore = await getWorkspaceCoreExports(cwd);
@@ -3504,7 +3521,9 @@ async function buildWithNitro() {
3504
3521
  }
3505
3522
  : null;
3506
3523
  const agentsBundleModuleSource = () => {
3507
- const bundle = readAgentsBundleFromFs(cwd, nitroWorkspaceSource);
3524
+ const bundle = readAgentsBundleFromFs(cwd, nitroWorkspaceSource, {
3525
+ instructions: nitroAgentConfig.instructions,
3526
+ });
3508
3527
  return `// AUTO-GENERATED by @agent-native/core deploy build (Nitro virtual)
3509
3528
  // Contains the inlined AGENTS.md + .agents/skills/ content from the template,
3510
3529
  // merged with the workspace core's AGENTS.md + skills/ when present.
@@ -3543,17 +3562,7 @@ export default bundle;
3543
3562
  virtual: {
3544
3563
  "virtual:agents-bundle": agentsBundleModuleSource,
3545
3564
  },
3546
- replace: {
3547
- // Netlify exposes DEPLOY_ID only while building. Embed it into the Nitro
3548
- // function so preview OAuth relays can target this immutable deployment
3549
- // even though the value is unavailable in the function runtime.
3550
- "process.env.AGENT_NATIVE_BUILD_ID": JSON.stringify(process.env.DEPLOY_ID?.trim() ||
3551
- process.env.AGENT_NATIVE_BUILD_ID?.trim() ||
3552
- ""),
3553
- "process.env.AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID": JSON.stringify(process.env.GA_MEASUREMENT_ID?.trim() || ""),
3554
- "process.env.AGENT_NATIVE_BUILD_GTM_CONTAINER_ID": JSON.stringify(process.env.GTM_CONTAINER_ID?.trim() || ""),
3555
- "process.env.AGENT_NATIVE_BUILD_DEPLOY_CONTEXT": JSON.stringify(process.env.CONTEXT?.trim() || ""),
3556
- },
3565
+ replace: resolveNitroBuildReplacements(),
3557
3566
  // Replace browser-only renderers (Excalidraw/Mermaid) with an inert proxy in
3558
3567
  // the server bundle. Without this, Nitro's Rolldown build pulls the real
3559
3568
  // Excalidraw into a shared vendor chunk imported statically by the SSR render
@@ -13,13 +13,13 @@
13
13
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
14
14
  count: number;
15
15
  updated?: undefined;
16
- ok?: undefined;
17
16
  error?: undefined;
17
+ ok?: undefined;
18
18
  } | {
19
19
  count?: undefined;
20
20
  updated: number;
21
- ok?: undefined;
22
21
  error?: undefined;
22
+ ok?: undefined;
23
23
  } | {
24
24
  count?: undefined;
25
25
  updated?: undefined;
@@ -28,6 +28,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
28
28
  } | {
29
29
  count?: undefined;
30
30
  updated?: undefined;
31
- ok: boolean;
32
31
  error?: undefined;
32
+ ok: boolean;
33
33
  }>>;
@@ -15,6 +15,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
15
15
  error: string;
16
16
  ok?: undefined;
17
17
  } | {
18
- ok: boolean;
19
18
  error?: undefined;
19
+ ok: boolean;
20
20
  }>>;