@agent-native/core 0.75.2 → 0.75.5

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 (44) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +86 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/durable-background.ts +57 -25
  5. package/corpus/core/src/agent/production-agent.ts +16 -13
  6. package/corpus/core/src/cli/skills.ts +22 -4
  7. package/corpus/core/src/cli/sync-builder-starter-manifest.ts +36 -12
  8. package/corpus/core/src/deploy/build.ts +79 -57
  9. package/corpus/core/src/deploy/workspace-deploy.ts +62 -30
  10. package/corpus/core/src/server/self-dispatch.ts +29 -2
  11. package/corpus/templates/clips/changelog/2026-06-24-chrome-extension-setup-now-shows-a-clear-all-done-confirmati.md +6 -0
  12. package/corpus/templates/clips/chrome-extension/src/permission.html +60 -0
  13. package/corpus/templates/clips/chrome-extension/src/permission.ts +28 -15
  14. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +6 -20
  15. package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +22 -4
  16. package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +22 -4
  17. package/corpus/templates/plan/app/components/plan/DocumentArea.tsx +45 -33
  18. package/corpus/templates/plan/changelog/2026-06-24-invalid-generated-recap-blocks-now-show-a-clear-warning-with.md +6 -0
  19. package/corpus/templates/plan/server/plan-content.ts +53 -3
  20. package/dist/agent/durable-background.d.ts +33 -24
  21. package/dist/agent/durable-background.d.ts.map +1 -1
  22. package/dist/agent/durable-background.js +56 -25
  23. package/dist/agent/durable-background.js.map +1 -1
  24. package/dist/agent/production-agent.d.ts.map +1 -1
  25. package/dist/agent/production-agent.js +16 -13
  26. package/dist/agent/production-agent.js.map +1 -1
  27. package/dist/cli/skills.d.ts +1 -1
  28. package/dist/cli/skills.d.ts.map +1 -1
  29. package/dist/cli/skills.js +22 -4
  30. package/dist/cli/skills.js.map +1 -1
  31. package/dist/cli/sync-builder-starter-manifest.d.ts.map +1 -1
  32. package/dist/cli/sync-builder-starter-manifest.js +21 -10
  33. package/dist/cli/sync-builder-starter-manifest.js.map +1 -1
  34. package/dist/deploy/build.d.ts +53 -33
  35. package/dist/deploy/build.d.ts.map +1 -1
  36. package/dist/deploy/build.js +79 -57
  37. package/dist/deploy/build.js.map +1 -1
  38. package/dist/deploy/workspace-deploy.js +62 -30
  39. package/dist/deploy/workspace-deploy.js.map +1 -1
  40. package/dist/server/self-dispatch.d.ts +0 -10
  41. package/dist/server/self-dispatch.d.ts.map +1 -1
  42. package/dist/server/self-dispatch.js +27 -2
  43. package/dist/server/self-dispatch.js.map +1 -1
  44. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 1160
31
- - template files: 4029
31
+ - template files: 4031
@@ -1,5 +1,91 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.75.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 25802f2: Durable background agent-chat runs now reach Netlify's 15-min async function via
8
+ the function's DEFAULT url (`/.netlify/functions/<name>`) with NO custom
9
+ `config.path` and NO catch-all patch — the doc-correct approach per the Netlify
10
+ docs.
11
+
12
+ Every Netlify function is reachable at `/.netlify/functions/<name>` by default;
13
+ a custom `config.path` REMOVES that default url. The build now emits the
14
+ background function into the scanned dir
15
+ (`.netlify/functions-internal/server-agent-background`, or per-app
16
+ `<app>-agent-background` for workspaces), sharing the same `main.mjs` bundle,
17
+ with `export const config = { background: true, ... }` and NO custom path. The
18
+ function therefore keeps its default url, and `background: true` makes any
19
+ invocation of that url asynchronous (immediate 202 ack, 15-min budget). The
20
+ Nitro `server` function's `/*` catch-all already excludes `/.netlify/*`, so the
21
+ default-url namespace is never shadowed by the synchronous function — there is
22
+ nothing to patch.
23
+
24
+ The function entry rewrites the incoming pathname to the framework
25
+ `_process-run` route (base-path-prefixed for workspaces) before delegating to
26
+ Nitro, preserving the method, all headers (the HMAC `Authorization: Bearer` the
27
+ plugin verifies), and the body. It also sets
28
+ `globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true` at cold start so the
29
+ worker takes the 15-min soft-timeout. The foreground self-dispatch resolves the
30
+ function's default url on hosted Netlify (per-app name from
31
+ `AGENT_NATIVE_WORKSPACE_APP_ID`), and `fireInternalDispatch` strips the app base
32
+ path for `/.netlify/*` targets so the request reaches the host-root function
33
+ url. Off-Netlify (local dev, `netlify dev`, non-Netlify hosts), the foreground
34
+ dispatches to the framework process-run route, handled inline by the same
35
+ in-process catch-all.
36
+
37
+ This supersedes the earlier attempt that gave the function a custom
38
+ `config.path` (the framework route) plus a `server` `excludedPath` patch — that
39
+ custom path was not honored as a route in production (a probe of
40
+ `POST /_agent-native/agent-chat/_process-run` returned 404). The graceful inline
41
+ 40s fallback on a dispatch fast-fail is unchanged.
42
+
43
+ ## 0.75.4
44
+
45
+ ### Patch Changes
46
+
47
+ - dbfbe42: Preserve starter-only manifest fields when syncing builder-agent-native-starter from templates/chat.
48
+
49
+ ## 0.75.3
50
+
51
+ ### Patch Changes
52
+
53
+ - 8a00851: Durable background agent-chat runs now reach Netlify's 15-min async function by
54
+ emitting the background function INTO the scanned functions dir with a real
55
+ `config.path`, and excluding that path from the Nitro `server` `/*` catch-all so
56
+ the match is unambiguous.
57
+
58
+ Grounded in the real Netlify build output: Nitro's `netlify` preset writes no
59
+ `netlify.toml` and no redirects — the `/*` catch-all is an in-code Functions API
60
+ v2 `config.path: "/*"` on `.netlify/functions-internal/server/server.mjs`.
61
+ Netlify scans exactly the configured `functionsDirectory`
62
+ (`.netlify/functions-internal`); `.netlify/functions/` is the build OUTPUT dir
63
+ (where `@netlify/build` later writes the zips + `manifest.json`) and is never
64
+ scanned. On CI, Netlify reads each scanned function's `export const config` to
65
+ build the manifest routes — so per-file `background`/`path` config is honored.
66
+
67
+ The build now emits the background function into
68
+ `.netlify/functions-internal/server-agent-background` (the scanned dir), sharing
69
+ the same `main.mjs` bundle, with `export const config = { background: true, path:
70
+ "/_agent-native/agent-chat/_process-run" }`. It also appends that path to the
71
+ `server` function's `config.excludedPath`, so the `/*` catch-all no longer
72
+ matches the process-run route. Netlify evaluates serverless functions before
73
+ redirects, so a POST to the framework process-run route matches only the async
74
+ background function (immediate 202 ack, 15-min budget) — never the synchronous
75
+ `server` catch-all. The entry sets
76
+ `globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true` at cold start and
77
+ normalizes the request path before delegating to Nitro, preserving the method,
78
+ all headers (the HMAC `Authorization: Bearer` the plugin verifies), and the body.
79
+
80
+ This supersedes the two earlier approaches that failed in production: emitting
81
+ into `functions-internal` with a `config.path` but WITHOUT excluding it from the
82
+ `/*` catch-all (both functions matched the path; the synchronous `server`
83
+ catch-all won, returning a sync 401 instead of a 202), and emitting a standalone
84
+ function into `.netlify/functions/` (never scanned, returned 404). The foreground
85
+ self-dispatch now always targets the framework process-run route on every host
86
+ via `resolveAgentChatProcessRunDispatchPath`. The graceful inline 40s fallback on
87
+ a dispatch fast-fail is unchanged.
88
+
3
89
  ## 0.75.2
4
90
 
5
91
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.75.2",
3
+ "version": "0.75.5",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -63,38 +63,67 @@ export const AGENT_CHAT_PROCESS_RUN_PATH =
63
63
  export const AGENT_BACKGROUND_FUNCTION_NAME = "server-agent-background";
64
64
 
65
65
  /**
66
- * Direct invocation URL of the standalone background function on Netlify.
67
- *
68
- * The function is emitted into the STANDARD functions dir with `background:true`
69
- * and NO custom `config.path`, so Netlify exposes it (and ONLY it) at the
70
- * default function URL `/.netlify/functions/<name>` and invokes it
71
- * asynchronously (immediate HTTP 202 ack, 15-min budget). Hitting this URL
72
- * directly BYPASSES Nitro's `/*` catch-all `server` function which is the
73
- * whole point: the previous approach put a custom `config.path` on the function
74
- * and Netlify routed `/_agent-native/agent-chat/_process-run` to the synchronous
75
- * `server` catch-all instead (verified live: a synchronous 401 from the handler
76
- * rather than a 202 async ack).
66
+ * Default function URL of the background function on Netlify. Every Netlify
67
+ * function is reachable at `/.netlify/functions/<name>` BY DEFAULT; that default
68
+ * url is removed ONLY if the function declares a custom `config.path`. The
69
+ * emitted background function declares NO custom `config.path` (it sets
70
+ * `background: true` and nothing else routing-related), so it KEEPS this default
71
+ * url and the Nitro `server` function already excludes `/.netlify/*` from its
72
+ * `/*` catch-all, so this namespace is never shadowed. The foreground therefore
73
+ * dispatches HERE on hosted Netlify (see `resolveAgentChatProcessRunDispatchPath`).
77
74
  */
78
75
  export const AGENT_BACKGROUND_FUNCTION_URL_PATH = `/.netlify/functions/${AGENT_BACKGROUND_FUNCTION_NAME}`;
79
76
 
77
+ /**
78
+ * The per-app workspace background function URL path. Workspace deploy emits one
79
+ * background function per app named `<app>-agent-background`, reachable at its
80
+ * DEFAULT url `/.netlify/functions/<app>-agent-background` (no custom
81
+ * `config.path`). The foreground resolves the current workspace app id from
82
+ * `AGENT_NATIVE_WORKSPACE_APP_ID` (set by the workspace function entry) so it can
83
+ * dispatch to the right per-app function url. Returns `null` when no workspace
84
+ * app id is configured (single-template deploy).
85
+ */
86
+ function resolveWorkspaceBackgroundFunctionUrlPath(): string | null {
87
+ const raw = process.env.AGENT_NATIVE_WORKSPACE_APP_ID;
88
+ if (typeof raw !== "string") return null;
89
+ // Mirror the workspace app-id normalization (resources/store.ts): take the
90
+ // first path segment and accept only the safe slug shape used for function
91
+ // names. Anything else falls back to the single-template name.
92
+ const candidate = raw.trim().replace(/^\/+/, "").split("/")[0] ?? "";
93
+ if (!/^[a-z0-9][a-z0-9-]{0,127}$/.test(candidate)) return null;
94
+ return `/.netlify/functions/${candidate}-agent-background`;
95
+ }
96
+
80
97
  /**
81
98
  * Resolve the path the foreground POST should self-dispatch the chat background
82
99
  * worker to.
83
100
  *
84
- * On hosted Netlify (`NETLIFY` truthy and not `netlify dev`) the standalone
85
- * async background function is reachable ONLY at its direct function URL
86
- * (`/.netlify/functions/server-agent-background`); POSTing the framework route
87
- * `AGENT_CHAT_PROCESS_RUN_PATH` would land on Nitro's synchronous `/*` catch-all
88
- * (the ~60s `server` function) and NEVER get the 15-min budget. The standalone
89
- * function's entry rewrites the incoming path back to `AGENT_CHAT_PROCESS_RUN_PATH`
90
- * before delegating to the Nitro handler, so the `_process-run` plugin still
91
- * dispatches it only the OUTER URL differs.
101
+ * GROUNDED IN THE REAL NETLIFY BUILD OUTPUT + THE NETLIFY DOCS DEFAULT-URL RULE:
102
+ * the background function is emitted INTO the scanned dir
103
+ * (`.netlify/functions-internal/server-agent-background`, or per-app
104
+ * `<app>-agent-background` for workspaces) with `export const config = {
105
+ * background: true, ... }` and NO custom `config.path`. Because it has no custom
106
+ * path, Netlify keeps its DEFAULT function url `/.netlify/functions/<name>`, and
107
+ * `background: true` makes any invocation of that url ASYNC (immediate 202,
108
+ * 15-min budget). The Nitro `server` function already excludes `/.netlify/*`
109
+ * from its `/*` catch-all, so the default-url namespace is NEVER shadowed by the
110
+ * synchronous function.
111
+ *
112
+ * Therefore on hosted Netlify the foreground dispatches to the function's DEFAULT
113
+ * url (`/.netlify/functions/<name>`); the function entry then rewrites the
114
+ * incoming pathname to `AGENT_CHAT_PROCESS_RUN_PATH` (base-path-prefixed for
115
+ * workspaces) before delegating to the Nitro router, so the `_process-run`
116
+ * plugin runs with the async 15-min budget. Everywhere else (local dev, `netlify
117
+ * dev`, non-Netlify hosts where no second function exists) there is no second
118
+ * function, so the foreground dispatches to the framework route
119
+ * `AGENT_CHAT_PROCESS_RUN_PATH` and the same in-process catch-all handles it
120
+ * inline. The HMAC token (signed over the runId) is unchanged either way.
92
121
  *
93
- * Everywhere else (local dev, `netlify dev`, non-Netlify hosts where the
94
- * standalone function does not exist) we keep dispatching to the framework route
95
- * directly the same in-process catch-all handles it, and there is no separate
96
- * async function to reach. The HMAC token (signed over the runId) is unchanged
97
- * either way; only the URL path differs.
122
+ * NOTE: this is the DOC-CORRECT approach. An earlier attempt gave the function a
123
+ * custom `config.path` + a catch-all `excludedPath` patch; the custom path was
124
+ * NOT honored as a route in prod (probe 404). Using the default function url
125
+ * (no custom path) is what Netlify documents and is simpler there is nothing
126
+ * to shadow because `/.netlify/*` is already excluded from the `server` catch-all.
98
127
  */
99
128
  export function resolveAgentChatProcessRunDispatchPath(): string {
100
129
  if (
@@ -102,7 +131,10 @@ export function resolveAgentChatProcessRunDispatchPath(): string {
102
131
  process.env.NETLIFY !== "false" &&
103
132
  process.env.NETLIFY_LOCAL !== "true"
104
133
  ) {
105
- return AGENT_BACKGROUND_FUNCTION_URL_PATH;
134
+ return (
135
+ resolveWorkspaceBackgroundFunctionUrlPath() ??
136
+ AGENT_BACKGROUND_FUNCTION_URL_PATH
137
+ );
106
138
  }
107
139
  return AGENT_CHAT_PROCESS_RUN_PATH;
108
140
  }
@@ -4394,14 +4394,16 @@ export function createProductionAgentHandler(
4394
4394
  try {
4395
4395
  await fireInternalDispatch({
4396
4396
  event,
4397
- // On hosted Netlify this is the standalone background function's DIRECT
4398
- // url (`/.netlify/functions/server-agent-background`) — POSTing the
4399
- // framework `_process-run` path would land on Nitro's synchronous `/*`
4400
- // catch-all and never get the 15-min budget. The function's entry
4401
- // rewrites the path back to AGENT_CHAT_PROCESS_RUN_PATH before
4402
- // delegating to Nitro, so the `_process-run` plugin still runs and the
4403
- // Authorization Bearer HMAC survives. Off-Netlify it stays the
4404
- // framework path (handled in-process).
4397
+ // On hosted Netlify this resolves to the background function's DEFAULT
4398
+ // url (/.netlify/functions/<name>, or per-app <app>-agent-background for
4399
+ // workspaces) the function declares NO custom config.path, so it keeps
4400
+ // its default url, and `background: true` makes that url async (202,
4401
+ // 15-min budget). The `server` /* catch-all already excludes /.netlify/*
4402
+ // so it never shadows it. Off-Netlify this resolves to the framework
4403
+ // `_process-run` route and the same in-process catch-all handles it
4404
+ // inline. `fireInternalDispatch` strips the app base path for
4405
+ // /.netlify/* targets so the request reaches the host-root function url;
4406
+ // the Authorization Bearer HMAC is preserved either way.
4405
4407
  path: resolveAgentChatProcessRunDispatchPath(),
4406
4408
  taskId: runId,
4407
4409
  body: {
@@ -4644,11 +4646,12 @@ export function createProductionAgentHandler(
4644
4646
  try {
4645
4647
  await fireInternalDispatch({
4646
4648
  event,
4647
- // Continuation chunks must also land on the standalone async
4648
- // background function (its direct url on hosted Netlify) so
4649
- // each chunk keeps the 15-min budget; same path-resolution as
4650
- // the initial dispatch. The function entry rewrites the path
4651
- // back to `_process-run` for the Nitro router.
4649
+ // Continuation chunks use the same path resolution as the
4650
+ // initial dispatch: on hosted Netlify the background
4651
+ // function's DEFAULT url (no custom config.path; async via
4652
+ // background:true; never shadowed because /.netlify/* is
4653
+ // excluded from the /* catch-all) so each chunk keeps the
4654
+ // 15-min budget; off-Netlify the in-process framework route.
4652
4655
  path: resolveAgentChatProcessRunDispatchPath(),
4653
4656
  taskId: nextRunId,
4654
4657
  body: {
@@ -526,6 +526,13 @@ themes. For any inline border, background, or text color, reference a token:
526
526
  and \`--wf-radius\`. Never hard-code a hex color and never set \`font-family\` — the
527
527
  renderer owns the sketch/clean font.
528
528
 
529
+ **Use literal CSS lengths for spacing.** The \`--wf-*\` tokens are for colors and
530
+ renderer-owned visual styling, not layout spacing. Do not use guessed spacing
531
+ tokens such as \`var(--wf-space-4)\`, Tailwind spacing classes, or theme spacing
532
+ variables inside wireframe HTML; if a token is unavailable in the Plan renderer,
533
+ padding collapses and content hugs the border. Use explicit CSS lengths for
534
+ layout: \`padding:16px\`, \`gap:12px\`, \`margin-top:18px\`, \`minmax(0,1fr)\`.
535
+
529
536
  **Lay out with inline \`style\` flex/grid.** You write the real layout —
530
537
  \`display:flex; flex-direction:column; gap:10px; padding:16px\` and so on — and the
531
538
  renderer never repositions anything. Compose the actual product: reproduce the
@@ -610,10 +617,21 @@ check/serve or verify command for \`<plan-dir>\`.
610
617
  **Treat the wireframe border as part of the visible design.** Always wrap HTML
611
618
  wireframe content in a root container with real inner padding before drawing
612
619
  cards, fields, pills, labels, or controls. Use at least 14-16px of padding,
613
- \`box-sizing: border-box\`, \`height: 100%\`, and \`gap\` between child rows so the
614
- first row never sits flush against the screen border. Keep text away from
615
- borders: every container, field, button, menu item, and annotation needs enough
616
- padding and line-height to read cleanly in the rendered Plan view.
620
+ \`box-sizing: border-box\`, \`height: 100%\`, and \`gap\` between child rows on the
621
+ root node itself so the first row never sits flush against the screen border. Do
622
+ not rely on padding on a nested page section as the first visible inset; the
623
+ outermost element must create the breathing room. Keep text away from borders:
624
+ every container, field, button, menu item, and annotation needs enough padding
625
+ and line-height to read cleanly in the rendered Plan view.
626
+
627
+ **For feature-cloud or abundance visuals, optimize the composition over line-by-line
628
+ reading.** Some marketing/product sections need to feel like a large surface area
629
+ of capability rather than a precise app workflow. In those cases, use one padded
630
+ root with a short headline and a dense, aesthetic cloud of short feature labels,
631
+ chips, rings, or columns. Vary scale and opacity with tokens, cluster by meaning,
632
+ and let many labels be glanceable rather than individually essential. Do not
633
+ force dozens of features into equal cards with long wrapped sentences; that
634
+ usually creates a messy unreadable mockup.
617
635
 
618
636
  **Lay out children safely so they never collide.** Use HTML flex/grid with
619
637
  \`gap\`, \`min-width: 0\`, and sensible overflow. Avoid negative margins, absolute
@@ -59,6 +59,26 @@ export function generateStandaloneChatManifest(repoRoot?: string): {
59
59
  }
60
60
  }
61
61
 
62
+ function mergePackageJsonRecords(
63
+ canonical: Record<string, string> | undefined,
64
+ starter: Record<string, string> | undefined,
65
+ starterPinnedKeys: string[] = [],
66
+ ): Record<string, string> {
67
+ const merged = { ...(canonical ?? {}) };
68
+ for (const [key, value] of Object.entries(starter ?? {})) {
69
+ if (!(key in merged)) {
70
+ merged[key] = value;
71
+ }
72
+ }
73
+ for (const key of starterPinnedKeys) {
74
+ const pinned = starter?.[key];
75
+ if (pinned) {
76
+ merged[key] = pinned;
77
+ }
78
+ }
79
+ return merged;
80
+ }
81
+
62
82
  export function mergeStarterManifest(
63
83
  starterPackageJson: PackageJson,
64
84
  canonicalPackageJson: PackageJson,
@@ -75,19 +95,23 @@ export function mergeStarterManifest(
75
95
  if (starterPackageJson.private !== undefined) {
76
96
  merged.private = starterPackageJson.private;
77
97
  }
98
+ if (typeof starterPackageJson.packageManager === "string") {
99
+ merged.packageManager = starterPackageJson.packageManager;
100
+ }
78
101
 
79
- const starterDeps =
80
- (starterPackageJson.dependencies as Record<string, string> | undefined) ??
81
- {};
82
- const canonicalDeps =
83
- (canonicalPackageJson.dependencies as Record<string, string> | undefined) ??
84
- {};
85
- merged.dependencies = {
86
- ...canonicalDeps,
87
- ...(starterDeps["@agent-native/core"]
88
- ? { "@agent-native/core": starterDeps["@agent-native/core"] }
89
- : {}),
90
- };
102
+ merged.dependencies = mergePackageJsonRecords(
103
+ canonicalPackageJson.dependencies as Record<string, string> | undefined,
104
+ starterPackageJson.dependencies as Record<string, string> | undefined,
105
+ ["@agent-native/core"],
106
+ );
107
+ merged.devDependencies = mergePackageJsonRecords(
108
+ canonicalPackageJson.devDependencies as Record<string, string> | undefined,
109
+ starterPackageJson.devDependencies as Record<string, string> | undefined,
110
+ );
111
+ merged.scripts = mergePackageJsonRecords(
112
+ canonicalPackageJson.scripts as Record<string, string> | undefined,
113
+ starterPackageJson.scripts as Record<string, string> | undefined,
114
+ );
91
115
 
92
116
  return merged;
93
117
  }
@@ -1505,46 +1505,66 @@ export function isDurableBackgroundDeployEnabled(): boolean {
1505
1505
  }
1506
1506
 
1507
1507
  /**
1508
- * Single-template Netlify build: emit a STANDALONE async background function so
1509
- * the chat `_process-run` worker runs on Netlify's async (15-min) function
1510
- * reached at its DIRECT url, instead of the synchronous `/*` catch-all.
1508
+ * Single-template Netlify build: emit an async (background) function INSIDE the
1509
+ * scanned functions dir so the chat `_process-run` worker runs on Netlify's
1510
+ * 15-min async function instead of the synchronous `/*` catch-all.
1511
1511
  * Additive + flag-gated (see `isDurableBackgroundDeployEnabled`).
1512
1512
  *
1513
- * Nitro's `netlify` preset emits a single synchronous function at
1514
- * `.netlify/functions-internal/server` (`server.mjs` `main.mjs`, `config.path`
1515
- * `/*`). We copy that bundle to a STANDALONE function in the STANDARD functions
1516
- * dir `.netlify/functions/server-agent-background` and write an entry that:
1517
- * 1. Sets `globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true` at cold start
1513
+ * GROUNDED IN THE REAL NETLIFY BUILD OUTPUT (verified from a local Nitro build)
1514
+ * AND THE NETLIFY DOCS DEFAULT-URL RULE:
1515
+ * - Nitro's `netlify` preset emits exactly ONE function source at
1516
+ * `.netlify/functions-internal/server/`. `server.mjs` re-exports `main.mjs`
1517
+ * and declares `export const config = { path: "/*", excludedPath:
1518
+ * ["/.netlify/*"], preferStatic: true, ... }`. The `/*` catch-all is an
1519
+ * IN-CODE Functions-API-v2 `config.path` and it ALREADY EXCLUDES
1520
+ * `/.netlify/*`.
1521
+ * - The generated `.netlify/netlify.toml` sets
1522
+ * `functionsDirectory = ".netlify/functions-internal"`. Netlify scans EXACTLY
1523
+ * that dir; functions placed anywhere else (e.g. `.netlify/functions/`, which
1524
+ * is the BUILD OUTPUT dir where `@netlify/build` later writes the zipped
1525
+ * functions + `manifest.json`) are NEVER deployed.
1526
+ * - Every scanned function is reachable at its DEFAULT url
1527
+ * `/.netlify/functions/<name>` BY DEFAULT. A custom `config.path` REMOVES
1528
+ * that default url; declaring NO custom `config.path` KEEPS it.
1529
+ *
1530
+ * THEREFORE we:
1531
+ * 1. Emit the background function INTO the scanned dir
1532
+ * (`.netlify/functions-internal/server-agent-background/`), sharing the same
1533
+ * built `main.mjs` bundle, so Netlify discovers it and honors its config.
1534
+ * 2. Give its `export const config` `background: true` (→ async invoke,
1535
+ * immediate 202, 15-min budget) and NO custom `config.path`. With no custom
1536
+ * path the function keeps its DEFAULT url
1537
+ * `/.netlify/functions/server-agent-background`, and because the Nitro
1538
+ * `server` function's `/*` catch-all already excludes `/.netlify/*`, that
1539
+ * default-url namespace is NEVER shadowed by the synchronous function — no
1540
+ * catch-all patch is needed.
1541
+ * 3. The entry NORMALIZES/rewrites the incoming request pathname to
1542
+ * `AGENT_CHAT_PROCESS_RUN_PATH` before delegating to `./main.mjs`. The
1543
+ * function is reached at its default url
1544
+ * (`/.netlify/functions/server-agent-background`), so the Nitro router needs
1545
+ * the path rewritten to the framework `_process-run` route, preserving the
1546
+ * method, ALL headers (the HMAC `Authorization: Bearer` MUST survive), and
1547
+ * the body.
1548
+ * 4. Set `globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true` at cold start
1518
1549
  * (read back by `isInBackgroundFunctionRuntime()` so the worker takes the
1519
1550
  * ~13-min soft-timeout). A `globalThis` flag — NOT `process.env` — keeps the
1520
1551
  * no-env-mutation guard satisfied and carries no cross-request state.
1521
- * 2. Declares `export const config = { background: true }` with NO `path`. With
1522
- * no custom `path`, Netlify exposes the function at the DEFAULT url
1523
- * `/.netlify/functions/server-agent-background` and `background:true` makes
1524
- * it ASYNC (immediate HTTP 202 ack, 15-min budget). The foreground POST
1525
- * self-dispatches to that direct url
1526
- * (`resolveAgentChatProcessRunDispatchPath`), BYPASSING Nitro's `/*`
1527
- * catch-all entirely.
1528
- * 3. REWRITES the incoming request path to `AGENT_CHAT_PROCESS_RUN_PATH` before
1529
- * delegating to the Nitro handler (`./main.mjs` default export — a Netlify
1530
- * v2 / Web-standard `async (Request) => Response` handler), so the Nitro
1531
- * router dispatches it to the `_process-run` plugin. Method, ALL headers
1532
- * (critically the HMAC `Authorization: Bearer` the plugin verifies) and the
1533
- * body are preserved by cloning the incoming `Request` with only its URL
1534
- * pathname rewritten.
1535
1552
  *
1536
- * WHY this replaced the previous `config.path` approach: a function with a custom
1537
- * `config.path` is reachable ONLY at that path (not at its default function url)
1538
- * and `functions-internal` is not exposed at the default url at all. In prod,
1539
- * Netlify routed `/_agent-native/agent-chat/_process-run` to the synchronous
1540
- * Nitro `server` catch-all, NOT to the background function verified live: a
1541
- * POST to the process-run path returned a SYNCHRONOUS 401 from the handler
1542
- * (the Nitro plugin ran) instead of a 202 async ack. A standalone function at
1543
- * its direct url cannot be shadowed by the catch-all.
1553
+ * The foreground dispatches to this DEFAULT url on hosted Netlify
1554
+ * (`resolveAgentChatProcessRunDispatchPath` `AGENT_BACKGROUND_FUNCTION_URL_PATH`).
1555
+ *
1556
+ * WHY THIS IS THE DOC-CORRECT FIX: a prior attempt gave the function a custom
1557
+ * `config.path` (= the framework route) plus a catch-all `excludedPath` patch.
1558
+ * The custom `config.path` was NOT honored as a route in prod a probe of
1559
+ * `POST /_agent-native/agent-chat/_process-run` returned 404. The doc-correct
1560
+ * approach (confirmed against the Netlify docs) is to use the DEFAULT function
1561
+ * url with no custom path: the function stays reachable at
1562
+ * `/.netlify/functions/<name>` and is never shadowed because `/.netlify/*` is
1563
+ * already excluded from the `server` catch-all.
1544
1564
  *
1545
- * Safety net regardless of Netlify routing nuance: if the direct-url dispatch
1546
- * fast-fails (e.g. 404 when this function was not emitted), the foreground
1547
- * handler degrades to an inline 40s synchronous run (see production-agent.ts).
1565
+ * Safety net regardless of Netlify routing nuance: if the dispatch fast-fails
1566
+ * (e.g. the function was not emitted), the foreground handler degrades to an
1567
+ * inline 40s synchronous run (see production-agent.ts).
1548
1568
  */
1549
1569
  export function emitSingleTemplateNetlifyBackgroundFunction(
1550
1570
  projectCwd: string,
@@ -1561,15 +1581,15 @@ export function emitSingleTemplateNetlifyBackgroundFunction(
1561
1581
  return;
1562
1582
  }
1563
1583
  const backgroundName = AGENT_BACKGROUND_FUNCTION_NAME;
1564
- // Emit into the STANDARD functions dir (NOT functions-internal) so Netlify
1565
- // exposes the function at its default url `/.netlify/functions/<name>`.
1566
- const functionsDir = path.join(projectCwd, ".netlify", "functions");
1567
- const dest = path.join(functionsDir, backgroundName);
1568
- fs.mkdirSync(functionsDir, { recursive: true });
1584
+ // Emit INTO the SCANNED functions dir (functions-internal) so Netlify discovers
1585
+ // the function and honors its `export const config`. `.netlify/functions/` is
1586
+ // the build OUTPUT dir (where @netlify/build writes the zip + manifest) and is
1587
+ // NOT scanned — emitting there is why the standalone attempt 404'd.
1588
+ const dest = path.join(internalDir, backgroundName);
1569
1589
  fs.rmSync(dest, { recursive: true, force: true });
1570
1590
  copyDir(serverDir, dest);
1571
- // Drop the original Nitro `/*` entry so our standalone entry is the entrypoint
1572
- // and the copied bundle does NOT re-register the catch-all `config.path`.
1591
+ // Drop the original Nitro `/*` entry so our entry is the entrypoint and the
1592
+ // copied bundle does NOT re-register the catch-all `config.path`.
1573
1593
  fs.rmSync(path.join(dest, "server.mjs"), { force: true });
1574
1594
 
1575
1595
  const processRunPath = JSON.stringify(AGENT_CHAT_PROCESS_RUN_PATH);
@@ -1589,12 +1609,12 @@ let cachedHandler;
1589
1609
 
1590
1610
  // Netlify v2 invokes this as (request, context). The Nitro netlify handler is a
1591
1611
  // Web-standard \`async (Request) => Response\` (see nitro/presets/netlify/runtime).
1592
- // We are reached at this function's DIRECT url
1593
- // (/.netlify/functions/${backgroundName}); rewrite the path to PROCESS_RUN_PATH
1594
- // so Nitro routes it to the _process-run plugin, preserving method, ALL headers
1595
- // (the HMAC Authorization: Bearer MUST survive the plugin verifies it) and the
1596
- // body. We clone the incoming Request with only its URL pathname rewritten;
1597
- // query + origin are preserved.
1612
+ // This function declares NO custom \`config.path\`, so it is reached at its
1613
+ // DEFAULT url (/.netlify/functions/${backgroundName}). The Nitro router only
1614
+ // knows the framework route, so we REWRITE the incoming pathname to
1615
+ // PROCESS_RUN_PATH before delegating. Method, ALL headers (the HMAC
1616
+ // Authorization: Bearer MUST survive — the plugin verifies it) and the body are
1617
+ // preserved by cloning the incoming Request with only its URL pathname set.
1598
1618
  export default async function handler(request) {
1599
1619
  cachedHandler ??= (await import("./main.mjs")).default;
1600
1620
  const url = new URL(request.url);
@@ -1615,13 +1635,12 @@ export const config = {
1615
1635
  name: "agent background handler",
1616
1636
  generator: "agent-native build",
1617
1637
  // background: true makes Netlify invoke this ASYNCHRONOUSLY (immediate HTTP
1618
- // 202 ack) with the 15-minute budget. NO custom \`path\`: that keeps the
1619
- // function reachable at its DEFAULT url /.netlify/functions/${backgroundName},
1620
- // which BYPASSES Nitro's /* catch-all. The previous version set a custom
1621
- // \`config.path\` and Netlify routed the process-run path to the SYNCHRONOUS
1622
- // catch-all instead (verified live: a synchronous 401 from the handler, not a
1623
- // 202 async ack). See Netlify docs: build/functions/background-functions +
1624
- // build/functions/configuration.
1638
+ // 202 ack) with the 15-minute budget (Netlify docs:
1639
+ // build/functions/background-functions + build/functions/api). We declare NO
1640
+ // custom path, so the function keeps its DEFAULT url
1641
+ // /.netlify/functions/${backgroundName}; the Nitro \`server\` /* catch-all
1642
+ // already excludes /.netlify/* so that default url is never shadowed by the
1643
+ // synchronous function. The foreground dispatches to that default url.
1625
1644
  background: true,
1626
1645
  nodeBundler: "none",
1627
1646
  includedFiles: ["**"],
@@ -1630,10 +1649,13 @@ export const config = {
1630
1649
  `;
1631
1650
  fs.writeFileSync(path.join(dest, `${backgroundName}.mjs`), entry);
1632
1651
  console.log(
1633
- `[build] Emitted standalone durable-background function "${backgroundName}" ` +
1634
- `at /.netlify/functions/${backgroundName} (async, rewrites to ` +
1635
- `${AGENT_CHAT_PROCESS_RUN_PATH}). REQUIRES real-deploy verification of ` +
1636
- `Netlify async invocation see docs/design/durable-agent-runs.md.`,
1652
+ `[build] Emitted durable-background function "${backgroundName}" into the ` +
1653
+ `scanned dir .netlify/functions-internal with config { background:true } ` +
1654
+ `and NO custom path reachable at its default url ` +
1655
+ `/.netlify/functions/${backgroundName} (never shadowed; the server /* ` +
1656
+ `catch-all already excludes /.netlify/*). REQUIRES real-deploy ` +
1657
+ `verification of Netlify async (202) invocation — see ` +
1658
+ `docs/design/durable-agent-runs.md.`,
1637
1659
  );
1638
1660
  }
1639
1661