@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +86 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +57 -25
- package/corpus/core/src/agent/production-agent.ts +16 -13
- package/corpus/core/src/cli/skills.ts +22 -4
- package/corpus/core/src/cli/sync-builder-starter-manifest.ts +36 -12
- package/corpus/core/src/deploy/build.ts +79 -57
- package/corpus/core/src/deploy/workspace-deploy.ts +62 -30
- package/corpus/core/src/server/self-dispatch.ts +29 -2
- package/corpus/templates/clips/changelog/2026-06-24-chrome-extension-setup-now-shows-a-clear-all-done-confirmati.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/permission.html +60 -0
- package/corpus/templates/clips/chrome-extension/src/permission.ts +28 -15
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +6 -20
- package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +22 -4
- package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +22 -4
- package/corpus/templates/plan/app/components/plan/DocumentArea.tsx +45 -33
- package/corpus/templates/plan/changelog/2026-06-24-invalid-generated-recap-blocks-now-show-a-clear-warning-with.md +6 -0
- package/corpus/templates/plan/server/plan-content.ts +53 -3
- package/dist/agent/durable-background.d.ts +33 -24
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +56 -25
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +16 -13
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/skills.d.ts +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +22 -4
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/sync-builder-starter-manifest.d.ts.map +1 -1
- package/dist/cli/sync-builder-starter-manifest.js +21 -10
- package/dist/cli/sync-builder-starter-manifest.js.map +1 -1
- package/dist/deploy/build.d.ts +53 -33
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +79 -57
- package/dist/deploy/build.js.map +1 -1
- package/dist/deploy/workspace-deploy.js +62 -30
- package/dist/deploy/workspace-deploy.js.map +1 -1
- package/dist/server/self-dispatch.d.ts +0 -10
- package/dist/server/self-dispatch.d.ts.map +1 -1
- package/dist/server/self-dispatch.js +27 -2
- package/dist/server/self-dispatch.js.map +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -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
|
package/corpus/core/package.json
CHANGED
|
@@ -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
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
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
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* (
|
|
87
|
-
* `
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
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
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
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
|
|
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
|
|
4398
|
-
// url (
|
|
4399
|
-
//
|
|
4400
|
-
//
|
|
4401
|
-
//
|
|
4402
|
-
//
|
|
4403
|
-
//
|
|
4404
|
-
//
|
|
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
|
|
4648
|
-
//
|
|
4649
|
-
//
|
|
4650
|
-
//
|
|
4651
|
-
//
|
|
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
|
|
614
|
-
first row never sits flush against the screen border.
|
|
615
|
-
|
|
616
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
1509
|
-
* the chat `_process-run` worker runs on Netlify's
|
|
1510
|
-
*
|
|
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
|
-
*
|
|
1514
|
-
*
|
|
1515
|
-
*
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
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
|
-
*
|
|
1537
|
-
* `
|
|
1538
|
-
*
|
|
1539
|
-
*
|
|
1540
|
-
*
|
|
1541
|
-
*
|
|
1542
|
-
*
|
|
1543
|
-
*
|
|
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
|
|
1546
|
-
*
|
|
1547
|
-
*
|
|
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
|
|
1565
|
-
//
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
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
|
|
1572
|
-
//
|
|
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
|
-
//
|
|
1593
|
-
// (/.netlify/functions/${backgroundName})
|
|
1594
|
-
//
|
|
1595
|
-
//
|
|
1596
|
-
//
|
|
1597
|
-
//
|
|
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
|
|
1619
|
-
//
|
|
1620
|
-
//
|
|
1621
|
-
//
|
|
1622
|
-
//
|
|
1623
|
-
//
|
|
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
|
|
1634
|
-
`
|
|
1635
|
-
|
|
1636
|
-
|
|
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
|
|