@agent-native/core 0.75.4 → 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 +40 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +62 -28
- package/corpus/core/src/agent/production-agent.ts +16 -12
- package/corpus/core/src/cli/skills.ts +22 -4
- package/corpus/core/src/deploy/build.ts +47 -113
- 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/plan/.agents/skills/visual-plan/references/wireframe.md +22 -4
- package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +22 -4
- package/dist/agent/durable-background.d.ts +32 -28
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +59 -28
- 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 -12
- 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/deploy/build.d.ts +32 -36
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +47 -104
- 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,45 @@
|
|
|
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
|
+
|
|
3
43
|
## 0.75.4
|
|
4
44
|
|
|
5
45
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -63,45 +63,79 @@ export const AGENT_CHAT_PROCESS_RUN_PATH =
|
|
|
63
63
|
export const AGENT_BACKGROUND_FUNCTION_NAME = "server-agent-background";
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* Default function URL of the background function on Netlify
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* the name/url shape stays asserted and discoverable.
|
|
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`).
|
|
75
74
|
*/
|
|
76
75
|
export const AGENT_BACKGROUND_FUNCTION_URL_PATH = `/.netlify/functions/${AGENT_BACKGROUND_FUNCTION_NAME}`;
|
|
77
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
|
+
|
|
78
97
|
/**
|
|
79
98
|
* Resolve the path the foreground POST should self-dispatch the chat background
|
|
80
99
|
* worker to.
|
|
81
100
|
*
|
|
82
|
-
* GROUNDED IN THE REAL NETLIFY BUILD OUTPUT
|
|
83
|
-
* INTO the scanned dir
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
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.
|
|
90
111
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
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.
|
|
97
121
|
*
|
|
98
|
-
* NOTE: this is
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
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.
|
|
103
127
|
*/
|
|
104
128
|
export function resolveAgentChatProcessRunDispatchPath(): string {
|
|
129
|
+
if (
|
|
130
|
+
process.env.NETLIFY &&
|
|
131
|
+
process.env.NETLIFY !== "false" &&
|
|
132
|
+
process.env.NETLIFY_LOCAL !== "true"
|
|
133
|
+
) {
|
|
134
|
+
return (
|
|
135
|
+
resolveWorkspaceBackgroundFunctionUrlPath() ??
|
|
136
|
+
AGENT_BACKGROUND_FUNCTION_URL_PATH
|
|
137
|
+
);
|
|
138
|
+
}
|
|
105
139
|
return AGENT_CHAT_PROCESS_RUN_PATH;
|
|
106
140
|
}
|
|
107
141
|
|
|
@@ -4394,13 +4394,16 @@ export function createProductionAgentHandler(
|
|
|
4394
4394
|
try {
|
|
4395
4395
|
await fireInternalDispatch({
|
|
4396
4396
|
event,
|
|
4397
|
-
//
|
|
4398
|
-
//
|
|
4399
|
-
//
|
|
4400
|
-
//
|
|
4401
|
-
//
|
|
4402
|
-
//
|
|
4403
|
-
//
|
|
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.
|
|
4404
4407
|
path: resolveAgentChatProcessRunDispatchPath(),
|
|
4405
4408
|
taskId: runId,
|
|
4406
4409
|
body: {
|
|
@@ -4643,11 +4646,12 @@ export function createProductionAgentHandler(
|
|
|
4643
4646
|
try {
|
|
4644
4647
|
await fireInternalDispatch({
|
|
4645
4648
|
event,
|
|
4646
|
-
// Continuation chunks
|
|
4647
|
-
//
|
|
4648
|
-
//
|
|
4649
|
-
//
|
|
4650
|
-
//
|
|
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.
|
|
4651
4655
|
path: resolveAgentChatProcessRunDispatchPath(),
|
|
4652
4656
|
taskId: nextRunId,
|
|
4653
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
|
|
@@ -1510,61 +1510,57 @@ export function isDurableBackgroundDeployEnabled(): boolean {
|
|
|
1510
1510
|
* 15-min async function instead of the synchronous `/*` catch-all.
|
|
1511
1511
|
* Additive + flag-gated (see `isDurableBackgroundDeployEnabled`).
|
|
1512
1512
|
*
|
|
1513
|
-
* GROUNDED IN THE REAL NETLIFY BUILD OUTPUT (verified from a local Nitro build)
|
|
1513
|
+
* GROUNDED IN THE REAL NETLIFY BUILD OUTPUT (verified from a local Nitro build)
|
|
1514
|
+
* AND THE NETLIFY DOCS DEFAULT-URL RULE:
|
|
1514
1515
|
* - Nitro's `netlify` preset emits exactly ONE function source at
|
|
1515
1516
|
* `.netlify/functions-internal/server/`. `server.mjs` re-exports `main.mjs`
|
|
1516
1517
|
* and declares `export const config = { path: "/*", excludedPath:
|
|
1517
|
-
* ["/.netlify/*"], preferStatic: true, ... }`.
|
|
1518
|
-
*
|
|
1519
|
-
*
|
|
1518
|
+
* ["/.netlify/*"], preferStatic: true, ... }`. The `/*` catch-all is an
|
|
1519
|
+
* IN-CODE Functions-API-v2 `config.path` and it ALREADY EXCLUDES
|
|
1520
|
+
* `/.netlify/*`.
|
|
1520
1521
|
* - The generated `.netlify/netlify.toml` sets
|
|
1521
1522
|
* `functionsDirectory = ".netlify/functions-internal"`. Netlify scans EXACTLY
|
|
1522
1523
|
* that dir; functions placed anywhere else (e.g. `.netlify/functions/`, which
|
|
1523
1524
|
* is the BUILD OUTPUT dir where `@netlify/build` later writes the zipped
|
|
1524
1525
|
* functions + `manifest.json`) are NEVER deployed.
|
|
1525
|
-
* -
|
|
1526
|
-
*
|
|
1527
|
-
*
|
|
1528
|
-
* `config.path`. So per-file `export const config` (including `background` and
|
|
1529
|
-
* `path`) IS honored — the `server` manifest entry's `routes: [{ pattern:
|
|
1530
|
-
* "/*" }]` came straight from `server.mjs`'s `config.path`.
|
|
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.
|
|
1531
1529
|
*
|
|
1532
1530
|
* THEREFORE we:
|
|
1533
1531
|
* 1. Emit the background function INTO the scanned dir
|
|
1534
1532
|
* (`.netlify/functions-internal/server-agent-background/`), sharing the same
|
|
1535
1533
|
* built `main.mjs` bundle, so Netlify discovers it and honors its config.
|
|
1536
|
-
* 2. Give its `export const config`
|
|
1537
|
-
* immediate 202, 15-min budget)
|
|
1538
|
-
*
|
|
1539
|
-
*
|
|
1540
|
-
*
|
|
1541
|
-
*
|
|
1542
|
-
*
|
|
1543
|
-
*
|
|
1544
|
-
*
|
|
1545
|
-
*
|
|
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.
|
|
1546
1548
|
* 4. Set `globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true` at cold start
|
|
1547
1549
|
* (read back by `isInBackgroundFunctionRuntime()` so the worker takes the
|
|
1548
1550
|
* ~13-min soft-timeout). A `globalThis` flag — NOT `process.env` — keeps the
|
|
1549
1551
|
* no-env-mutation guard satisfied and carries no cross-request state.
|
|
1550
1552
|
*
|
|
1551
|
-
*
|
|
1552
|
-
*
|
|
1553
|
-
* (`resolveAgentChatProcessRunDispatchPath`) and the request entry no longer
|
|
1554
|
-
* needs to rewrite the path — it already arrives at the framework route. We keep
|
|
1555
|
-
* a defensive normalize-to-PROCESS_RUN_PATH in the entry anyway (cheap, and it
|
|
1556
|
-
* makes the function correct even if reached via its default function url).
|
|
1553
|
+
* The foreground dispatches to this DEFAULT url on hosted Netlify
|
|
1554
|
+
* (`resolveAgentChatProcessRunDispatchPath` → `AGENT_BACKGROUND_FUNCTION_URL_PATH`).
|
|
1557
1555
|
*
|
|
1558
|
-
* WHY THIS
|
|
1559
|
-
*
|
|
1560
|
-
*
|
|
1561
|
-
*
|
|
1562
|
-
*
|
|
1563
|
-
*
|
|
1564
|
-
*
|
|
1565
|
-
*
|
|
1566
|
-
* OUTPUT dir, which Netlify does not scan — so it never entered the manifest
|
|
1567
|
-
* → 404. We now emit into the SCANNED `functions-internal` dir.
|
|
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.
|
|
1568
1564
|
*
|
|
1569
1565
|
* Safety net regardless of Netlify routing nuance: if the dispatch fast-fails
|
|
1570
1566
|
* (e.g. the function was not emitted), the foreground handler degrades to an
|
|
@@ -1596,11 +1592,6 @@ export function emitSingleTemplateNetlifyBackgroundFunction(
|
|
|
1596
1592
|
// copied bundle does NOT re-register the catch-all `config.path`.
|
|
1597
1593
|
fs.rmSync(path.join(dest, "server.mjs"), { force: true });
|
|
1598
1594
|
|
|
1599
|
-
// Make the `server` `/*` catch-all NOT match the process-run path, so only the
|
|
1600
|
-
// async background function matches it (function-vs-function path order is
|
|
1601
|
-
// undocumented on Netlify — don't rely on it).
|
|
1602
|
-
excludeProcessRunPathFromServerCatchAll(serverDir);
|
|
1603
|
-
|
|
1604
1595
|
const processRunPath = JSON.stringify(AGENT_CHAT_PROCESS_RUN_PATH);
|
|
1605
1596
|
const entry = `// Mark this isolate as the durable background runtime BEFORE the handler
|
|
1606
1597
|
// bundle is imported, so isInBackgroundFunctionRuntime() reliably returns true
|
|
@@ -1618,11 +1609,10 @@ let cachedHandler;
|
|
|
1618
1609
|
|
|
1619
1610
|
// Netlify v2 invokes this as (request, context). The Nitro netlify handler is a
|
|
1620
1611
|
// Web-standard \`async (Request) => Response\` (see nitro/presets/netlify/runtime).
|
|
1621
|
-
//
|
|
1622
|
-
//
|
|
1623
|
-
//
|
|
1624
|
-
//
|
|
1625
|
-
// (/.netlify/functions/${backgroundName}). Method, ALL headers (the HMAC
|
|
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
|
|
1626
1616
|
// Authorization: Bearer MUST survive — the plugin verifies it) and the body are
|
|
1627
1617
|
// preserved by cloning the incoming Request with only its URL pathname set.
|
|
1628
1618
|
export default async function handler(request) {
|
|
@@ -1646,13 +1636,12 @@ export const config = {
|
|
|
1646
1636
|
generator: "agent-native build",
|
|
1647
1637
|
// background: true makes Netlify invoke this ASYNCHRONOUSLY (immediate HTTP
|
|
1648
1638
|
// 202 ack) with the 15-minute budget (Netlify docs:
|
|
1649
|
-
// build/functions/background-functions + build/functions/api).
|
|
1650
|
-
//
|
|
1651
|
-
//
|
|
1652
|
-
//
|
|
1653
|
-
// function
|
|
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.
|
|
1654
1644
|
background: true,
|
|
1655
|
-
path: PROCESS_RUN_PATH,
|
|
1656
1645
|
nodeBundler: "none",
|
|
1657
1646
|
includedFiles: ["**"],
|
|
1658
1647
|
preferStatic: false,
|
|
@@ -1661,67 +1650,12 @@ export const config = {
|
|
|
1661
1650
|
fs.writeFileSync(path.join(dest, `${backgroundName}.mjs`), entry);
|
|
1662
1651
|
console.log(
|
|
1663
1652
|
`[build] Emitted durable-background function "${backgroundName}" into the ` +
|
|
1664
|
-
`scanned dir .netlify/functions-internal with config { background:true
|
|
1665
|
-
`path
|
|
1666
|
-
|
|
1667
|
-
`
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
/**
|
|
1672
|
-
* Append `AGENT_CHAT_PROCESS_RUN_PATH` to the Nitro `server` function's
|
|
1673
|
-
* `config.excludedPath` so its `/*` catch-all does NOT match the process-run
|
|
1674
|
-
* path. That guarantees the async background function (which declares
|
|
1675
|
-
* `config.path = AGENT_CHAT_PROCESS_RUN_PATH`) is the ONLY function that matches
|
|
1676
|
-
* that path — Netlify does not define a winner when two serverless functions
|
|
1677
|
-
* both match, so we make the match unambiguous instead of relying on order.
|
|
1678
|
-
*
|
|
1679
|
-
* The Nitro-generated `server/server.mjs` is small and deterministic:
|
|
1680
|
-
* export { default } from "./main.mjs";
|
|
1681
|
-
* export const config = { ... excludedPath: ["/.netlify/*"], ... };
|
|
1682
|
-
* We parse the `excludedPath: [...]` array literal and add our path if absent.
|
|
1683
|
-
* If the shape ever changes and we can't find/parse it, we log and leave the
|
|
1684
|
-
* file untouched (the inline-40s fallback still keeps chat working).
|
|
1685
|
-
*/
|
|
1686
|
-
function excludeProcessRunPathFromServerCatchAll(serverDir: string): void {
|
|
1687
|
-
const serverEntry = path.join(serverDir, "server.mjs");
|
|
1688
|
-
if (!fs.existsSync(serverEntry)) {
|
|
1689
|
-
console.warn(
|
|
1690
|
-
"[build] Durable-background: server/server.mjs not found; cannot exclude " +
|
|
1691
|
-
`${AGENT_CHAT_PROCESS_RUN_PATH} from the /* catch-all.`,
|
|
1692
|
-
);
|
|
1693
|
-
return;
|
|
1694
|
-
}
|
|
1695
|
-
const original = fs.readFileSync(serverEntry, "utf8");
|
|
1696
|
-
if (original.includes(AGENT_CHAT_PROCESS_RUN_PATH)) {
|
|
1697
|
-
// Already excluded (idempotent — emit may run on a re-used output tree).
|
|
1698
|
-
return;
|
|
1699
|
-
}
|
|
1700
|
-
const excludedPathRe = /excludedPath:\s*\[([^\]]*)\]/;
|
|
1701
|
-
const match = original.match(excludedPathRe);
|
|
1702
|
-
const quotedPath = JSON.stringify(AGENT_CHAT_PROCESS_RUN_PATH);
|
|
1703
|
-
if (match) {
|
|
1704
|
-
const existing = match[1].trim();
|
|
1705
|
-
const next = existing
|
|
1706
|
-
? `excludedPath: [${existing.replace(/,\s*$/, "")}, ${quotedPath}]`
|
|
1707
|
-
: `excludedPath: [${quotedPath}]`;
|
|
1708
|
-
fs.writeFileSync(serverEntry, original.replace(excludedPathRe, next));
|
|
1709
|
-
return;
|
|
1710
|
-
}
|
|
1711
|
-
// No existing `excludedPath` — inject one into the config object. Match the
|
|
1712
|
-
// `path: "/*"` line and add `excludedPath` right after it.
|
|
1713
|
-
const pathLineRe = /(path:\s*("\/\*"|'\/\*'),?)/;
|
|
1714
|
-
if (pathLineRe.test(original)) {
|
|
1715
|
-
fs.writeFileSync(
|
|
1716
|
-
serverEntry,
|
|
1717
|
-
original.replace(pathLineRe, `$1\n excludedPath: [${quotedPath}],`),
|
|
1718
|
-
);
|
|
1719
|
-
return;
|
|
1720
|
-
}
|
|
1721
|
-
console.warn(
|
|
1722
|
-
"[build] Durable-background: could not locate excludedPath/path in " +
|
|
1723
|
-
"server/server.mjs; leaving the /* catch-all unchanged (the background " +
|
|
1724
|
-
"function may be shadowed — the inline-40s fallback still applies).",
|
|
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.`,
|
|
1725
1659
|
);
|
|
1726
1660
|
}
|
|
1727
1661
|
|
|
@@ -696,24 +696,30 @@ function isDurableBackgroundDeployEnabled(): boolean {
|
|
|
696
696
|
/**
|
|
697
697
|
* Emit a SECOND Netlify function for `app` whose name ends in `-background`,
|
|
698
698
|
* re-exporting the SAME `main.mjs` handler bundle. Netlify invokes any function
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
*
|
|
699
|
+
* with `config.background: true` asynchronously (202 immediately, up to 15-min
|
|
700
|
+
* budget), which is exactly what the durable-background chat dispatch
|
|
701
|
+
* (`fireInternalDispatch` → the function's default url) needs.
|
|
702
702
|
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
703
|
+
* DOC-CORRECT DEFAULT-URL APPROACH (mirrors the single-template emit in
|
|
704
|
+
* deploy/build.ts): the function declares NO custom `config.path`, so it keeps
|
|
705
|
+
* its DEFAULT url `/.netlify/functions/<app>-agent-background`. The `<app>-server`
|
|
706
|
+
* function's catch-all already excludes `/.netlify/*`, so that default-url
|
|
707
|
+
* namespace is NEVER shadowed by the synchronous function — no overlapping
|
|
708
|
+
* `config.path` and no catch-all patch are needed. The foreground dispatches to
|
|
709
|
+
* that default url (`resolveAgentChatProcessRunDispatchPath` resolves the per-app
|
|
710
|
+
* name from `AGENT_NATIVE_WORKSPACE_APP_ID`); `fireInternalDispatch` strips the
|
|
711
|
+
* app base path for `/.netlify/*` targets so the request reaches the host-root
|
|
712
|
+
* function url. The entry then REWRITES the incoming pathname to the
|
|
713
|
+
* base-path-prefixed `_process-run` route before delegating to the Nitro router.
|
|
707
714
|
*
|
|
708
|
-
*
|
|
709
|
-
*
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*
|
|
715
|
-
*
|
|
716
|
-
* win. See packages/core/docs/design/durable-agent-runs.md.
|
|
715
|
+
* It shares the same bundle (`includedFiles: ["**"]`) so `A2A_SECRET`, the DB
|
|
716
|
+
* URL, and the rest of the env/bundle are present. A prior attempt gave the
|
|
717
|
+
* function a custom `config.path` (the framework route) that overlapped the
|
|
718
|
+
* synchronous `<app>-server` catch-all; that path was not honored as a route in
|
|
719
|
+
* prod (probe → 404). The default url is the doc-correct fix.
|
|
720
|
+
*
|
|
721
|
+
* Safety net: if the dispatch fast-fails the foreground degrades to an inline
|
|
722
|
+
* 40s synchronous run (see production-agent.ts).
|
|
717
723
|
*/
|
|
718
724
|
function emitNetlifyBackgroundFunction(
|
|
719
725
|
workspaceRoot: string,
|
|
@@ -721,7 +727,9 @@ function emitNetlifyBackgroundFunction(
|
|
|
721
727
|
srcServerDir: string,
|
|
722
728
|
workspaceApps: WorkspaceAppManifestEntry[],
|
|
723
729
|
): void {
|
|
724
|
-
// Name MUST end in `-background`
|
|
730
|
+
// Name MUST end in `-background` (Netlify async convention + the runtime guard
|
|
731
|
+
// reads the -background Lambda-name suffix as a fallback). It is reached at its
|
|
732
|
+
// DEFAULT url /.netlify/functions/<app>-agent-background.
|
|
725
733
|
const backgroundName = `${app}-agent-background`;
|
|
726
734
|
const dest = path.join(netlifyFunctionsDir(workspaceRoot), backgroundName);
|
|
727
735
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
@@ -733,14 +741,20 @@ function emitNetlifyBackgroundFunction(
|
|
|
733
741
|
workspaceApps,
|
|
734
742
|
app,
|
|
735
743
|
);
|
|
736
|
-
//
|
|
737
|
-
//
|
|
738
|
-
//
|
|
739
|
-
// processor dispatch), so the route Netlify must map to this async function
|
|
740
|
-
// is `/<app>/_agent-native/agent-chat/_process-run`.
|
|
744
|
+
// The Nitro router for this app expects the base-path-prefixed framework route.
|
|
745
|
+
// The function is reached at its default url, so the entry rewrites the
|
|
746
|
+
// incoming pathname to `/<app>/_agent-native/agent-chat/_process-run`.
|
|
741
747
|
const processRunPath = `${basePath}${AGENT_CHAT_PROCESS_RUN_PATH}`;
|
|
742
|
-
const
|
|
743
|
-
|
|
748
|
+
const server = `// Mark this isolate as the durable background runtime BEFORE the handler bundle
|
|
749
|
+
// is imported, so isInBackgroundFunctionRuntime() reliably returns true in this
|
|
750
|
+
// function (the deployed Lambda name is not guaranteed to end in -background). A
|
|
751
|
+
// globalThis flag (NOT process.env) avoids the no-env-mutation guard and carries
|
|
752
|
+
// no cross-request state.
|
|
753
|
+
globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true;
|
|
754
|
+
|
|
755
|
+
const basePath = ${JSON.stringify(basePath)};
|
|
756
|
+
// The base-path-prefixed framework route the Nitro router dispatches to.
|
|
757
|
+
const PROCESS_RUN_PATH = ${JSON.stringify(processRunPath)};
|
|
744
758
|
|
|
745
759
|
function setBasePathEnv() {
|
|
746
760
|
const processRef = globalThis.process ??= { env: {} };
|
|
@@ -767,16 +781,33 @@ setBasePathEnv();
|
|
|
767
781
|
|
|
768
782
|
let cachedHandler;
|
|
769
783
|
|
|
770
|
-
|
|
784
|
+
// Reached at the DEFAULT url /.netlify/functions/${backgroundName}; REWRITE the
|
|
785
|
+
// incoming pathname to the base-path-prefixed _process-run route so the Nitro
|
|
786
|
+
// router runs the plugin. Method, ALL headers (the HMAC Authorization: Bearer
|
|
787
|
+
// MUST survive) and the body are preserved.
|
|
788
|
+
export default async function handler(request) {
|
|
771
789
|
setBasePathEnv();
|
|
772
790
|
cachedHandler ??= (await import("./main.mjs")).default;
|
|
773
|
-
|
|
791
|
+
const url = new URL(request.url);
|
|
792
|
+
url.pathname = PROCESS_RUN_PATH;
|
|
793
|
+
const method = request.method || "POST";
|
|
794
|
+
const hasBody = method !== "GET" && method !== "HEAD";
|
|
795
|
+
const body = hasBody ? await request.text() : undefined;
|
|
796
|
+
const rewritten = new Request(url.toString(), {
|
|
797
|
+
method,
|
|
798
|
+
headers: request.headers,
|
|
799
|
+
body,
|
|
800
|
+
});
|
|
801
|
+
return cachedHandler(rewritten);
|
|
774
802
|
}
|
|
775
803
|
|
|
776
804
|
export const config = {
|
|
777
805
|
name: ${JSON.stringify(`${app} agent background handler`)},
|
|
778
806
|
generator: "agent-native workspace deploy",
|
|
779
|
-
|
|
807
|
+
// background: true → async invoke (202, 15-min budget). NO custom path: the
|
|
808
|
+
// function keeps its default url /.netlify/functions/${backgroundName}, which
|
|
809
|
+
// the <app>-server catch-all never shadows (it excludes /.netlify/*).
|
|
810
|
+
background: true,
|
|
780
811
|
nodeBundler: "none",
|
|
781
812
|
includedFiles: ["**"],
|
|
782
813
|
preferStatic: false,
|
|
@@ -788,9 +819,10 @@ export const config = {
|
|
|
788
819
|
fs.writeFileSync(path.join(dest, `${backgroundName}.mjs`), server);
|
|
789
820
|
console.log(
|
|
790
821
|
`[workspace-deploy] Emitted durable-background function "${backgroundName}" ` +
|
|
791
|
-
`for app "${app}"
|
|
792
|
-
`
|
|
793
|
-
`
|
|
822
|
+
`for app "${app}" with config { background:true } and NO custom path — ` +
|
|
823
|
+
`reachable at its default url /.netlify/functions/${backgroundName} ` +
|
|
824
|
+
`(rewrites to ${processRunPath}). REQUIRES real-deploy verification of ` +
|
|
825
|
+
`Netlify async (202) invocation — see docs/design/durable-agent-runs.md.`,
|
|
794
826
|
);
|
|
795
827
|
}
|
|
796
828
|
|