@agent-native/core 0.75.2 → 0.75.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +46 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +29 -31
- package/corpus/core/src/agent/production-agent.ts +12 -13
- package/corpus/core/src/cli/sync-builder-starter-manifest.ts +36 -12
- package/corpus/core/src/deploy/build.ts +145 -57
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +6 -20
- 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 +29 -24
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +29 -29
- 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 +12 -13
- package/dist/agent/production-agent.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 +57 -33
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +136 -57
- package/dist/deploy/build.js.map +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.75.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- dbfbe42: Preserve starter-only manifest fields when syncing builder-agent-native-starter from templates/chat.
|
|
8
|
+
|
|
9
|
+
## 0.75.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 8a00851: Durable background agent-chat runs now reach Netlify's 15-min async function by
|
|
14
|
+
emitting the background function INTO the scanned functions dir with a real
|
|
15
|
+
`config.path`, and excluding that path from the Nitro `server` `/*` catch-all so
|
|
16
|
+
the match is unambiguous.
|
|
17
|
+
|
|
18
|
+
Grounded in the real Netlify build output: Nitro's `netlify` preset writes no
|
|
19
|
+
`netlify.toml` and no redirects — the `/*` catch-all is an in-code Functions API
|
|
20
|
+
v2 `config.path: "/*"` on `.netlify/functions-internal/server/server.mjs`.
|
|
21
|
+
Netlify scans exactly the configured `functionsDirectory`
|
|
22
|
+
(`.netlify/functions-internal`); `.netlify/functions/` is the build OUTPUT dir
|
|
23
|
+
(where `@netlify/build` later writes the zips + `manifest.json`) and is never
|
|
24
|
+
scanned. On CI, Netlify reads each scanned function's `export const config` to
|
|
25
|
+
build the manifest routes — so per-file `background`/`path` config is honored.
|
|
26
|
+
|
|
27
|
+
The build now emits the background function into
|
|
28
|
+
`.netlify/functions-internal/server-agent-background` (the scanned dir), sharing
|
|
29
|
+
the same `main.mjs` bundle, with `export const config = { background: true, path:
|
|
30
|
+
"/_agent-native/agent-chat/_process-run" }`. It also appends that path to the
|
|
31
|
+
`server` function's `config.excludedPath`, so the `/*` catch-all no longer
|
|
32
|
+
matches the process-run route. Netlify evaluates serverless functions before
|
|
33
|
+
redirects, so a POST to the framework process-run route matches only the async
|
|
34
|
+
background function (immediate 202 ack, 15-min budget) — never the synchronous
|
|
35
|
+
`server` catch-all. The entry sets
|
|
36
|
+
`globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true` at cold start and
|
|
37
|
+
normalizes the request path before delegating to Nitro, preserving the method,
|
|
38
|
+
all headers (the HMAC `Authorization: Bearer` the plugin verifies), and the body.
|
|
39
|
+
|
|
40
|
+
This supersedes the two earlier approaches that failed in production: emitting
|
|
41
|
+
into `functions-internal` with a `config.path` but WITHOUT excluding it from the
|
|
42
|
+
`/*` catch-all (both functions matched the path; the synchronous `server`
|
|
43
|
+
catch-all won, returning a sync 401 instead of a 202), and emitting a standalone
|
|
44
|
+
function into `.netlify/functions/` (never scanned, returned 404). The foreground
|
|
45
|
+
self-dispatch now always targets the framework process-run route on every host
|
|
46
|
+
via `resolveAgentChatProcessRunDispatchPath`. The graceful inline 40s fallback on
|
|
47
|
+
a dispatch fast-fail is unchanged.
|
|
48
|
+
|
|
3
49
|
## 0.75.2
|
|
4
50
|
|
|
5
51
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -63,17 +63,15 @@ 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
|
-
*
|
|
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, kept for
|
|
67
|
+
* diagnostics/tests. Every Netlify function is ALSO reachable at
|
|
68
|
+
* `/.netlify/functions/<name>` unless a custom `config.path` removes the default
|
|
69
|
+
* url. The emitted background function declares `config.path =
|
|
70
|
+
* AGENT_CHAT_PROCESS_RUN_PATH`, which means Netlify routes the process-run path
|
|
71
|
+
* to it directly AND (per Netlify docs) removes this default url — so the
|
|
72
|
+
* foreground does NOT dispatch here; it dispatches to the framework route (see
|
|
73
|
+
* `resolveAgentChatProcessRunDispatchPath`). This constant is retained only so
|
|
74
|
+
* the name/url shape stays asserted and discoverable.
|
|
77
75
|
*/
|
|
78
76
|
export const AGENT_BACKGROUND_FUNCTION_URL_PATH = `/.netlify/functions/${AGENT_BACKGROUND_FUNCTION_NAME}`;
|
|
79
77
|
|
|
@@ -81,29 +79,29 @@ export const AGENT_BACKGROUND_FUNCTION_URL_PATH = `/.netlify/functions/${AGENT_B
|
|
|
81
79
|
* Resolve the path the foreground POST should self-dispatch the chat background
|
|
82
80
|
* worker to.
|
|
83
81
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* (
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
82
|
+
* GROUNDED IN THE REAL NETLIFY BUILD OUTPUT: the background function is emitted
|
|
83
|
+
* INTO the scanned dir (`.netlify/functions-internal/server-agent-background`)
|
|
84
|
+
* with `export const config = { background: true, path:
|
|
85
|
+
* AGENT_CHAT_PROCESS_RUN_PATH }`. Netlify evaluates serverless functions BEFORE
|
|
86
|
+
* redirects (request-chain step 10 vs 11), and the build excludes this exact path
|
|
87
|
+
* from the `server` `/*` catch-all — so a POST to `AGENT_CHAT_PROCESS_RUN_PATH`
|
|
88
|
+
* matches ONLY the async background function (immediate 202, 15-min budget),
|
|
89
|
+
* never the synchronous `server` catch-all.
|
|
90
|
+
*
|
|
91
|
+
* So the dispatch path is the SAME framework route on every host. On hosted
|
|
92
|
+
* Netlify it lands on the async function (because of the exclude + the
|
|
93
|
+
* background function's `config.path`); everywhere else (local dev, `netlify
|
|
94
|
+
* dev`, non-Netlify hosts where no second function exists) the same in-process
|
|
95
|
+
* catch-all handles it inline. The HMAC token (signed over the runId) is
|
|
96
|
+
* unchanged.
|
|
92
97
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
+
* NOTE: this is a deliberate change from the earlier "dispatch to the direct
|
|
99
|
+
* `/.netlify/functions/<name>` url" attempt, which only worked if the function
|
|
100
|
+
* was reachable at its default url. With a custom `config.path` the default url
|
|
101
|
+
* is removed, and there is no shadowing to bypass anyway, so dispatching to the
|
|
102
|
+
* framework route is both correct and simpler.
|
|
98
103
|
*/
|
|
99
104
|
export function resolveAgentChatProcessRunDispatchPath(): string {
|
|
100
|
-
if (
|
|
101
|
-
process.env.NETLIFY &&
|
|
102
|
-
process.env.NETLIFY !== "false" &&
|
|
103
|
-
process.env.NETLIFY_LOCAL !== "true"
|
|
104
|
-
) {
|
|
105
|
-
return AGENT_BACKGROUND_FUNCTION_URL_PATH;
|
|
106
|
-
}
|
|
107
105
|
return AGENT_CHAT_PROCESS_RUN_PATH;
|
|
108
106
|
}
|
|
109
107
|
|
|
@@ -4394,14 +4394,13 @@ export function createProductionAgentHandler(
|
|
|
4394
4394
|
try {
|
|
4395
4395
|
await fireInternalDispatch({
|
|
4396
4396
|
event,
|
|
4397
|
-
//
|
|
4398
|
-
//
|
|
4399
|
-
//
|
|
4400
|
-
// catch-all
|
|
4401
|
-
//
|
|
4402
|
-
//
|
|
4403
|
-
// Authorization Bearer HMAC
|
|
4404
|
-
// framework path (handled in-process).
|
|
4397
|
+
// The framework `_process-run` route on every host. On hosted Netlify
|
|
4398
|
+
// the build emits an async background function (in the scanned dir)
|
|
4399
|
+
// that CLAIMS this exact path via `config.path` AND excludes it from
|
|
4400
|
+
// the `server` /* catch-all, so this POST matches ONLY the async
|
|
4401
|
+
// function (immediate 202, 15-min budget) — Netlify matches functions
|
|
4402
|
+
// before redirects. Off-Netlify the same in-process catch-all handles
|
|
4403
|
+
// it. The Authorization Bearer HMAC is preserved either way.
|
|
4405
4404
|
path: resolveAgentChatProcessRunDispatchPath(),
|
|
4406
4405
|
taskId: runId,
|
|
4407
4406
|
body: {
|
|
@@ -4644,11 +4643,11 @@ export function createProductionAgentHandler(
|
|
|
4644
4643
|
try {
|
|
4645
4644
|
await fireInternalDispatch({
|
|
4646
4645
|
event,
|
|
4647
|
-
// Continuation chunks
|
|
4648
|
-
//
|
|
4649
|
-
//
|
|
4650
|
-
//
|
|
4651
|
-
//
|
|
4646
|
+
// Continuation chunks dispatch to the same framework
|
|
4647
|
+
// `_process-run` route; on hosted Netlify it matches the
|
|
4648
|
+
// async background function (config.path + excluded from the
|
|
4649
|
+
// /* catch-all) so each chunk keeps the 15-min budget. Same
|
|
4650
|
+
// path-resolution as the initial dispatch.
|
|
4652
4651
|
path: resolveAgentChatProcessRunDispatchPath(),
|
|
4653
4652
|
taskId: nextRunId,
|
|
4654
4653
|
body: {
|
|
@@ -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,70 @@ 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
|
+
* - Nitro's `netlify` preset emits exactly ONE function source at
|
|
1515
|
+
* `.netlify/functions-internal/server/`. `server.mjs` re-exports `main.mjs`
|
|
1516
|
+
* and declares `export const config = { path: "/*", excludedPath:
|
|
1517
|
+
* ["/.netlify/*"], preferStatic: true, ... }`. Nitro writes NO `netlify.toml`
|
|
1518
|
+
* and NO `[[redirects]]`; the `/*` catch-all is an IN-CODE Functions-API-v2
|
|
1519
|
+
* `config.path`.
|
|
1520
|
+
* - The generated `.netlify/netlify.toml` sets
|
|
1521
|
+
* `functionsDirectory = ".netlify/functions-internal"`. Netlify scans EXACTLY
|
|
1522
|
+
* that dir; functions placed anywhere else (e.g. `.netlify/functions/`, which
|
|
1523
|
+
* is the BUILD OUTPUT dir where `@netlify/build` later writes the zipped
|
|
1524
|
+
* functions + `manifest.json`) are NEVER deployed.
|
|
1525
|
+
* - On Netlify CI, `@netlify/build` reads each scanned function's
|
|
1526
|
+
* `export const config`, zips it, and materializes
|
|
1527
|
+
* `.netlify/functions/manifest.json` with `routes` derived from each
|
|
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`.
|
|
1531
|
+
*
|
|
1532
|
+
* THEREFORE we:
|
|
1533
|
+
* 1. Emit the background function INTO the scanned dir
|
|
1534
|
+
* (`.netlify/functions-internal/server-agent-background/`), sharing the same
|
|
1535
|
+
* built `main.mjs` bundle, so Netlify discovers it and honors its config.
|
|
1536
|
+
* 2. Give its `export const config` BOTH `background: true` (→ async invoke,
|
|
1537
|
+
* immediate 202, 15-min budget) AND `path: AGENT_CHAT_PROCESS_RUN_PATH` (so
|
|
1538
|
+
* it claims that exact path at function-matching time, which Netlify
|
|
1539
|
+
* evaluates BEFORE redirects — step 10 vs 11 in the request chain).
|
|
1540
|
+
* 3. PATCH the Nitro `server` function's own `server.mjs` so its catch-all
|
|
1541
|
+
* `config.path: "/*"` EXCLUDES `AGENT_CHAT_PROCESS_RUN_PATH` (append it to
|
|
1542
|
+
* `excludedPath`). Netlify does NOT define a winner when two serverless
|
|
1543
|
+
* functions both match a path; rather than rely on that undocumented order,
|
|
1544
|
+
* we make the match UNAMBIGUOUS — only the background function matches
|
|
1545
|
+
* `_process-run`, and `server` matches everything else exactly as before.
|
|
1546
|
+
* 4. Set `globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true` at cold start
|
|
1518
1547
|
* (read back by `isInBackgroundFunctionRuntime()` so the worker takes the
|
|
1519
1548
|
* ~13-min soft-timeout). A `globalThis` flag — NOT `process.env` — keeps the
|
|
1520
1549
|
* 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
1550
|
*
|
|
1536
|
-
*
|
|
1537
|
-
*
|
|
1538
|
-
*
|
|
1539
|
-
*
|
|
1540
|
-
*
|
|
1541
|
-
*
|
|
1542
|
-
*
|
|
1543
|
-
*
|
|
1551
|
+
* With a real `config.path` the function is reachable at that path directly, so
|
|
1552
|
+
* the foreground POSTs to `AGENT_CHAT_PROCESS_RUN_PATH`
|
|
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).
|
|
1557
|
+
*
|
|
1558
|
+
* WHY THIS BEATS ALL THREE PRIOR FAILURES:
|
|
1559
|
+
* - Attempts 1 & 2 emitted into `functions-internal` (correct dir) with a
|
|
1560
|
+
* `config.path` but did NOT exclude that path from the `server` `/*`
|
|
1561
|
+
* catch-all. Two functions matched `_process-run`; the order is undocumented
|
|
1562
|
+
* and the catch-all `server` (priority 0, synchronous) won → SYNC 401, not a
|
|
1563
|
+
* 202. We now exclude the path from `server`, so only the async function
|
|
1564
|
+
* matches.
|
|
1565
|
+
* - Attempt 3 emitted a standalone function into `.netlify/functions/` — the
|
|
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.
|
|
1544
1568
|
*
|
|
1545
|
-
* Safety net regardless of Netlify routing nuance: if the
|
|
1546
|
-
*
|
|
1547
|
-
*
|
|
1569
|
+
* Safety net regardless of Netlify routing nuance: if the dispatch fast-fails
|
|
1570
|
+
* (e.g. the function was not emitted), the foreground handler degrades to an
|
|
1571
|
+
* inline 40s synchronous run (see production-agent.ts).
|
|
1548
1572
|
*/
|
|
1549
1573
|
export function emitSingleTemplateNetlifyBackgroundFunction(
|
|
1550
1574
|
projectCwd: string,
|
|
@@ -1561,17 +1585,22 @@ export function emitSingleTemplateNetlifyBackgroundFunction(
|
|
|
1561
1585
|
return;
|
|
1562
1586
|
}
|
|
1563
1587
|
const backgroundName = AGENT_BACKGROUND_FUNCTION_NAME;
|
|
1564
|
-
// Emit
|
|
1565
|
-
//
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1588
|
+
// Emit INTO the SCANNED functions dir (functions-internal) so Netlify discovers
|
|
1589
|
+
// the function and honors its `export const config`. `.netlify/functions/` is
|
|
1590
|
+
// the build OUTPUT dir (where @netlify/build writes the zip + manifest) and is
|
|
1591
|
+
// NOT scanned — emitting there is why the standalone attempt 404'd.
|
|
1592
|
+
const dest = path.join(internalDir, backgroundName);
|
|
1569
1593
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
1570
1594
|
copyDir(serverDir, dest);
|
|
1571
|
-
// Drop the original Nitro `/*` entry so our
|
|
1572
|
-
//
|
|
1595
|
+
// Drop the original Nitro `/*` entry so our entry is the entrypoint and the
|
|
1596
|
+
// copied bundle does NOT re-register the catch-all `config.path`.
|
|
1573
1597
|
fs.rmSync(path.join(dest, "server.mjs"), { force: true });
|
|
1574
1598
|
|
|
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
|
+
|
|
1575
1604
|
const processRunPath = JSON.stringify(AGENT_CHAT_PROCESS_RUN_PATH);
|
|
1576
1605
|
const entry = `// Mark this isolate as the durable background runtime BEFORE the handler
|
|
1577
1606
|
// bundle is imported, so isInBackgroundFunctionRuntime() reliably returns true
|
|
@@ -1589,12 +1618,13 @@ let cachedHandler;
|
|
|
1589
1618
|
|
|
1590
1619
|
// Netlify v2 invokes this as (request, context). The Nitro netlify handler is a
|
|
1591
1620
|
// Web-standard \`async (Request) => Response\` (see nitro/presets/netlify/runtime).
|
|
1592
|
-
//
|
|
1593
|
-
//
|
|
1594
|
-
//
|
|
1595
|
-
//
|
|
1596
|
-
//
|
|
1597
|
-
//
|
|
1621
|
+
// Because this function declares \`config.path = PROCESS_RUN_PATH\`, Netlify routes
|
|
1622
|
+
// that exact path here and the request already arrives as PROCESS_RUN_PATH — no
|
|
1623
|
+
// rewrite is needed. We still NORMALIZE the pathname to PROCESS_RUN_PATH so the
|
|
1624
|
+
// function stays correct even if it is ever reached via its default function url
|
|
1625
|
+
// (/.netlify/functions/${backgroundName}). Method, ALL headers (the HMAC
|
|
1626
|
+
// Authorization: Bearer MUST survive — the plugin verifies it) and the body are
|
|
1627
|
+
// preserved by cloning the incoming Request with only its URL pathname set.
|
|
1598
1628
|
export default async function handler(request) {
|
|
1599
1629
|
cachedHandler ??= (await import("./main.mjs")).default;
|
|
1600
1630
|
const url = new URL(request.url);
|
|
@@ -1615,14 +1645,14 @@ export const config = {
|
|
|
1615
1645
|
name: "agent background handler",
|
|
1616
1646
|
generator: "agent-native build",
|
|
1617
1647
|
// background: true makes Netlify invoke this ASYNCHRONOUSLY (immediate HTTP
|
|
1618
|
-
// 202 ack) with the 15-minute budget
|
|
1619
|
-
//
|
|
1620
|
-
//
|
|
1621
|
-
//
|
|
1622
|
-
// catch-all
|
|
1623
|
-
//
|
|
1624
|
-
// build/functions/configuration.
|
|
1648
|
+
// 202 ack) with the 15-minute budget (Netlify docs:
|
|
1649
|
+
// build/functions/background-functions + build/functions/api). path claims the
|
|
1650
|
+
// process-run route directly; Netlify evaluates serverless functions BEFORE
|
|
1651
|
+
// redirects (request-chain step 10 vs 11), and we exclude this path from the
|
|
1652
|
+
// \`server\` /* catch-all so only THIS function matches it — no ambiguous
|
|
1653
|
+
// function-vs-function order.
|
|
1625
1654
|
background: true,
|
|
1655
|
+
path: PROCESS_RUN_PATH,
|
|
1626
1656
|
nodeBundler: "none",
|
|
1627
1657
|
includedFiles: ["**"],
|
|
1628
1658
|
preferStatic: false,
|
|
@@ -1630,10 +1660,68 @@ export const config = {
|
|
|
1630
1660
|
`;
|
|
1631
1661
|
fs.writeFileSync(path.join(dest, `${backgroundName}.mjs`), entry);
|
|
1632
1662
|
console.log(
|
|
1633
|
-
`[build] Emitted
|
|
1634
|
-
`
|
|
1635
|
-
|
|
1636
|
-
`Netlify async
|
|
1663
|
+
`[build] Emitted durable-background function "${backgroundName}" into the ` +
|
|
1664
|
+
`scanned dir .netlify/functions-internal with config { background:true, ` +
|
|
1665
|
+
`path:"${AGENT_CHAT_PROCESS_RUN_PATH}" } and excluded that path from the ` +
|
|
1666
|
+
`server /* catch-all. REQUIRES real-deploy verification of Netlify async ` +
|
|
1667
|
+
`(202) invocation — see docs/design/durable-agent-runs.md.`,
|
|
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).",
|
|
1637
1725
|
);
|
|
1638
1726
|
}
|
|
1639
1727
|
|
|
@@ -2349,7 +2349,6 @@ export function AddProperty({
|
|
|
2349
2349
|
},
|
|
2350
2350
|
});
|
|
2351
2351
|
const [open, setOpen] = useState(false);
|
|
2352
|
-
const [name, setName] = useState("");
|
|
2353
2352
|
const [typeQuery, setTypeQuery] = useState("");
|
|
2354
2353
|
const filteredPropertyTypes = filterDocumentPropertyTypes(typeQuery);
|
|
2355
2354
|
const firstFilteredPropertyType = filteredPropertyTypes[0] ?? null;
|
|
@@ -2377,19 +2376,18 @@ export function AddProperty({
|
|
|
2377
2376
|
}),
|
|
2378
2377
|
}))
|
|
2379
2378
|
.filter((group) => group.fields.length > 0);
|
|
2380
|
-
const
|
|
2379
|
+
const addPropertySearchInputRef = useRef<HTMLInputElement>(null);
|
|
2381
2380
|
|
|
2382
2381
|
useEffect(() => {
|
|
2383
2382
|
if (!open) return;
|
|
2384
2383
|
const frame = requestAnimationFrame(() => {
|
|
2385
|
-
|
|
2386
|
-
|
|
2384
|
+
addPropertySearchInputRef.current?.focus();
|
|
2385
|
+
addPropertySearchInputRef.current?.select();
|
|
2387
2386
|
});
|
|
2388
2387
|
return () => cancelAnimationFrame(frame);
|
|
2389
2388
|
}, [open]);
|
|
2390
2389
|
|
|
2391
2390
|
function closeAddPropertyPicker() {
|
|
2392
|
-
setName("");
|
|
2393
2391
|
setTypeQuery("");
|
|
2394
2392
|
setOpen(false);
|
|
2395
2393
|
}
|
|
@@ -2398,7 +2396,7 @@ export function AddProperty({
|
|
|
2398
2396
|
const label = DOCUMENT_PROPERTY_TYPE_LABELS[type];
|
|
2399
2397
|
await configure.mutateAsync({
|
|
2400
2398
|
documentId,
|
|
2401
|
-
name:
|
|
2399
|
+
name: label,
|
|
2402
2400
|
type,
|
|
2403
2401
|
options: defaultPropertyOptions(type),
|
|
2404
2402
|
});
|
|
@@ -2445,23 +2443,11 @@ export function AddProperty({
|
|
|
2445
2443
|
className="w-80 p-2"
|
|
2446
2444
|
>
|
|
2447
2445
|
<div className="grid gap-2">
|
|
2448
|
-
<Input
|
|
2449
|
-
ref={addPropertyNameInputRef}
|
|
2450
|
-
aria-label="New property name"
|
|
2451
|
-
autoFocus
|
|
2452
|
-
value={name}
|
|
2453
|
-
placeholder="Property name"
|
|
2454
|
-
onChange={(event) => setName(event.target.value)}
|
|
2455
|
-
onKeyDown={(event) => {
|
|
2456
|
-
if (event.key === "Escape") {
|
|
2457
|
-
event.preventDefault();
|
|
2458
|
-
closeAddPropertyPicker();
|
|
2459
|
-
}
|
|
2460
|
-
}}
|
|
2461
|
-
/>
|
|
2462
2446
|
<div className="flex h-8 items-center gap-1 rounded border border-border bg-background px-2">
|
|
2463
2447
|
<IconSearch className="size-3.5 shrink-0 text-muted-foreground" />
|
|
2464
2448
|
<Input
|
|
2449
|
+
ref={addPropertySearchInputRef}
|
|
2450
|
+
autoFocus
|
|
2465
2451
|
value={typeQuery}
|
|
2466
2452
|
placeholder="Search property types"
|
|
2467
2453
|
aria-label="Search property types"
|