@agent-native/core 0.75.0 → 0.75.2
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 +71 -3
- package/corpus/core/src/agent/production-agent.ts +16 -3
- package/corpus/core/src/cli/skills.ts +15 -11
- package/corpus/core/src/deploy/build.ts +100 -30
- package/corpus/core/src/templates/default/_gitignore +1 -0
- package/corpus/core/src/templates/headless/_gitignore +1 -0
- package/corpus/core/src/templates/workspace-root/_gitignore +1 -0
- package/corpus/templates/analytics/actions/get-sql-dashboard.ts +0 -8
- package/corpus/templates/analytics/actions/update-dashboard.ts +3 -1
- package/corpus/templates/analytics/app/components/SqlEditor.tsx +57 -0
- package/corpus/templates/analytics/app/components/SqlHighlight.tsx +27 -28
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +130 -8
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +2 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/ViewSqlPopover.tsx +4 -4
- package/corpus/templates/analytics/changelog/2026-06-24-active-user-dashboard-panels-classify-templates-more-accurat.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-panel-deletes-now-stay-deleted-after-refresh.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-sql-panel-editors-now-highlight-query-syntax-and-keep-one-cl.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +2 -2
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +9 -11
- package/corpus/templates/clips/actions/list-recordings.ts +21 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +21 -2
- package/corpus/templates/clips/app/hooks/use-library.ts +18 -0
- package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +7 -5
- package/dist/agent/durable-background.d.ts +51 -3
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +64 -3
- 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 -3
- 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 +15 -11
- package/dist/cli/skills.js.map +1 -1
- package/dist/deploy/build.d.ts +38 -17
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +96 -29
- package/dist/deploy/build.js.map +1 -1
- package/dist/templates/default/_gitignore +1 -0
- package/dist/templates/headless/_gitignore +1 -0
- package/dist/templates/workspace-root/_gitignore +1 -0
- package/package.json +1 -1
- package/src/templates/default/_gitignore +1 -0
- package/src/templates/headless/_gitignore +1 -0
- package/src/templates/workspace-root/_gitignore +1 -0
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.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4b3543d: Durable background agent-chat runs now actually receive Netlify's 15-min async
|
|
8
|
+
budget by reaching a STANDALONE background function at its DIRECT url, bypassing
|
|
9
|
+
Nitro's synchronous `/*` catch-all.
|
|
10
|
+
|
|
11
|
+
The previous emit wrote the background function into `.netlify/functions-internal`
|
|
12
|
+
with a custom `config.path` of the `_process-run` route. A custom `config.path`
|
|
13
|
+
makes a function reachable ONLY at that path (not at its default function url),
|
|
14
|
+
`functions-internal` is not exposed at a default url, and Netlify routed
|
|
15
|
+
`/_agent-native/agent-chat/_process-run` to the synchronous Nitro `server`
|
|
16
|
+
catch-all instead — so the worker was capped at the ~60s wall and degraded to
|
|
17
|
+
40s-chunked runs (confirmed live: a POST to the process-run path returned a
|
|
18
|
+
synchronous 401 from the handler rather than a 202 async ack).
|
|
19
|
+
|
|
20
|
+
The build now emits a standalone function into the STANDARD
|
|
21
|
+
`.netlify/functions/server-agent-background` dir with `background: true` and NO
|
|
22
|
+
custom `path`, so Netlify exposes it at `/.netlify/functions/server-agent-background`
|
|
23
|
+
and invokes it asynchronously (immediate 202 ack, 15-min budget). Its entry sets
|
|
24
|
+
`globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true` at cold start and rewrites
|
|
25
|
+
the incoming request path back to the `_process-run` route before delegating to
|
|
26
|
+
the Nitro handler, preserving the method, all headers (the HMAC
|
|
27
|
+
`Authorization: Bearer` the plugin verifies survives the rewrite), and the body.
|
|
28
|
+
The foreground self-dispatch (and server-driven continuation chunks) now target
|
|
29
|
+
that direct url on hosted Netlify via a shared `resolveAgentChatProcessRunDispatchPath`
|
|
30
|
+
helper, and stay on the framework route everywhere else. The graceful inline
|
|
31
|
+
40s fallback on a dispatch fast-fail is unchanged.
|
|
32
|
+
|
|
33
|
+
## 0.75.1
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- 98b89e2: Durable background agent-chat runs now actually receive Netlify's 15-min async
|
|
38
|
+
budget. The emitted `-background` function declared a custom `config.path` but
|
|
39
|
+
omitted `background: true`, so Netlify served it SYNCHRONOUSLY (~60s) — the
|
|
40
|
+
`config` object overrides the legacy `-background` filename convention — and the
|
|
41
|
+
durable worker was capped at the 60s wall (it degraded to 40s-chunked runs and
|
|
42
|
+
never used the 15-min budget). The emit now sets `background: true` (immediate
|
|
43
|
+
202 ack + 15-min execution) and force-marks the background runtime in the
|
|
44
|
+
function entry so the worker reliably takes the ~13-min soft-timeout regardless
|
|
45
|
+
of the deployed Lambda name. Root cause confirmed with a live prod routing probe
|
|
46
|
+
(POST to the process-run path returned a synchronous 401 from the handler
|
|
47
|
+
instead of a 202 async ack).
|
|
48
|
+
|
|
3
49
|
## 0.75.0
|
|
4
50
|
|
|
5
51
|
### Minor Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -51,6 +51,62 @@ import {
|
|
|
51
51
|
export const AGENT_CHAT_PROCESS_RUN_PATH =
|
|
52
52
|
"/_agent-native/agent-chat/_process-run";
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Name of the standalone Netlify background function the build emits (see
|
|
56
|
+
* `emitSingleTemplateNetlifyBackgroundFunction` in deploy/build.ts). Shared so
|
|
57
|
+
* the emit and the dispatch-path helper below can never drift on the name.
|
|
58
|
+
*
|
|
59
|
+
* MUST end in `-background` — both because that is the conventional Netlify
|
|
60
|
+
* async-function suffix and because `isInBackgroundFunctionRuntime()` reads the
|
|
61
|
+
* `AWS_LAMBDA_FUNCTION_NAME` `-background` suffix as a secondary runtime signal.
|
|
62
|
+
*/
|
|
63
|
+
export const AGENT_BACKGROUND_FUNCTION_NAME = "server-agent-background";
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Direct invocation URL of the standalone background function on Netlify.
|
|
67
|
+
*
|
|
68
|
+
* The function is emitted into the STANDARD functions dir with `background:true`
|
|
69
|
+
* and NO custom `config.path`, so Netlify exposes it (and ONLY it) at the
|
|
70
|
+
* default function URL `/.netlify/functions/<name>` and invokes it
|
|
71
|
+
* asynchronously (immediate HTTP 202 ack, 15-min budget). Hitting this URL
|
|
72
|
+
* directly BYPASSES Nitro's `/*` catch-all `server` function — which is the
|
|
73
|
+
* whole point: the previous approach put a custom `config.path` on the function
|
|
74
|
+
* and Netlify routed `/_agent-native/agent-chat/_process-run` to the synchronous
|
|
75
|
+
* `server` catch-all instead (verified live: a synchronous 401 from the handler
|
|
76
|
+
* rather than a 202 async ack).
|
|
77
|
+
*/
|
|
78
|
+
export const AGENT_BACKGROUND_FUNCTION_URL_PATH = `/.netlify/functions/${AGENT_BACKGROUND_FUNCTION_NAME}`;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolve the path the foreground POST should self-dispatch the chat background
|
|
82
|
+
* worker to.
|
|
83
|
+
*
|
|
84
|
+
* On hosted Netlify (`NETLIFY` truthy and not `netlify dev`) the standalone
|
|
85
|
+
* async background function is reachable ONLY at its direct function URL
|
|
86
|
+
* (`/.netlify/functions/server-agent-background`); POSTing the framework route
|
|
87
|
+
* `AGENT_CHAT_PROCESS_RUN_PATH` would land on Nitro's synchronous `/*` catch-all
|
|
88
|
+
* (the ~60s `server` function) and NEVER get the 15-min budget. The standalone
|
|
89
|
+
* function's entry rewrites the incoming path back to `AGENT_CHAT_PROCESS_RUN_PATH`
|
|
90
|
+
* before delegating to the Nitro handler, so the `_process-run` plugin still
|
|
91
|
+
* dispatches it — only the OUTER URL differs.
|
|
92
|
+
*
|
|
93
|
+
* Everywhere else (local dev, `netlify dev`, non-Netlify hosts where the
|
|
94
|
+
* standalone function does not exist) we keep dispatching to the framework route
|
|
95
|
+
* directly — the same in-process catch-all handles it, and there is no separate
|
|
96
|
+
* async function to reach. The HMAC token (signed over the runId) is unchanged
|
|
97
|
+
* either way; only the URL path differs.
|
|
98
|
+
*/
|
|
99
|
+
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
|
+
return AGENT_CHAT_PROCESS_RUN_PATH;
|
|
108
|
+
}
|
|
109
|
+
|
|
54
110
|
/**
|
|
55
111
|
* Env flag for durable background runs. DEFAULT-ON: unset means enabled; an app
|
|
56
112
|
* opts OUT with an explicit falsy value (`false`/`0`/`no`/`off`).
|
|
@@ -112,11 +168,23 @@ export function isHostedRuntimeForDurableBackground(): boolean {
|
|
|
112
168
|
* hard wall and get killed at 60s, then re-dispatch/resume in a wasteful loop.
|
|
113
169
|
* So the 13-min budget is taken ONLY when this returns true.
|
|
114
170
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
171
|
+
* The PRIMARY signal is a `globalThis` marker the emitted background function's
|
|
172
|
+
* entry sets at cold start — the deployed Lambda name is not guaranteed to end
|
|
173
|
+
* in `-background` on Netlify, so the entry marks its own runtime. A `globalThis`
|
|
174
|
+
* flag (not `process.env`) keeps the no-env-mutation guard satisfied and carries
|
|
175
|
+
* no cross-request state (set once per isolate). The `AWS_LAMBDA_FUNCTION_NAME`
|
|
176
|
+
* suffix and the explicit `AGENT_CHAT_FORCE_BACKGROUND_RUNTIME` env (truthy) are
|
|
177
|
+
* additional signals — the latter an operator escape hatch. Off by default.
|
|
118
178
|
*/
|
|
119
179
|
export function isInBackgroundFunctionRuntime(): boolean {
|
|
180
|
+
// Set by the emitted `-background` function entry at cold start (the primary,
|
|
181
|
+
// most reliable signal — see the emit in deploy/build.ts).
|
|
182
|
+
if (
|
|
183
|
+
(globalThis as Record<string, unknown>)
|
|
184
|
+
.__AGENT_NATIVE_BACKGROUND_RUNTIME__ === true
|
|
185
|
+
) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
120
188
|
const lambdaName = process.env.AWS_LAMBDA_FUNCTION_NAME;
|
|
121
189
|
if (
|
|
122
190
|
typeof lambdaName === "string" &&
|
|
@@ -65,10 +65,10 @@ import {
|
|
|
65
65
|
} from "./run-manager.js";
|
|
66
66
|
import type { ActiveRun } from "./run-manager.js";
|
|
67
67
|
import {
|
|
68
|
-
AGENT_CHAT_PROCESS_RUN_PATH,
|
|
69
68
|
AGENT_CHAT_BACKGROUND_RUN_FIELD,
|
|
70
69
|
isAgentChatDurableBackgroundEnabled,
|
|
71
70
|
isInBackgroundFunctionRuntime,
|
|
71
|
+
resolveAgentChatProcessRunDispatchPath,
|
|
72
72
|
} from "./durable-background.js";
|
|
73
73
|
import { fireInternalDispatch } from "../server/self-dispatch.js";
|
|
74
74
|
import { readBody } from "../server/h3-helpers.js";
|
|
@@ -4394,7 +4394,15 @@ export function createProductionAgentHandler(
|
|
|
4394
4394
|
try {
|
|
4395
4395
|
await fireInternalDispatch({
|
|
4396
4396
|
event,
|
|
4397
|
-
|
|
4397
|
+
// On hosted Netlify this is the standalone background function's DIRECT
|
|
4398
|
+
// url (`/.netlify/functions/server-agent-background`) — POSTing the
|
|
4399
|
+
// framework `_process-run` path would land on Nitro's synchronous `/*`
|
|
4400
|
+
// catch-all and never get the 15-min budget. The function's entry
|
|
4401
|
+
// rewrites the path back to AGENT_CHAT_PROCESS_RUN_PATH before
|
|
4402
|
+
// delegating to Nitro, so the `_process-run` plugin still runs and the
|
|
4403
|
+
// Authorization Bearer HMAC survives. Off-Netlify it stays the
|
|
4404
|
+
// framework path (handled in-process).
|
|
4405
|
+
path: resolveAgentChatProcessRunDispatchPath(),
|
|
4398
4406
|
taskId: runId,
|
|
4399
4407
|
body: {
|
|
4400
4408
|
...body,
|
|
@@ -4636,7 +4644,12 @@ export function createProductionAgentHandler(
|
|
|
4636
4644
|
try {
|
|
4637
4645
|
await fireInternalDispatch({
|
|
4638
4646
|
event,
|
|
4639
|
-
|
|
4647
|
+
// Continuation chunks must also land on the standalone async
|
|
4648
|
+
// background function (its direct url on hosted Netlify) so
|
|
4649
|
+
// each chunk keeps the 15-min budget; same path-resolution as
|
|
4650
|
+
// the initial dispatch. The function entry rewrites the path
|
|
4651
|
+
// back to `_process-run` for the Nitro router.
|
|
4652
|
+
path: resolveAgentChatProcessRunDispatchPath(),
|
|
4640
4653
|
taskId: nextRunId,
|
|
4641
4654
|
body: {
|
|
4642
4655
|
...body,
|
|
@@ -394,14 +394,16 @@ sign-in at setup — this is intended), so the first tool call in that client do
|
|
|
394
394
|
not hit an OAuth wall:
|
|
395
395
|
|
|
396
396
|
\`\`\`bash
|
|
397
|
-
npx @agent-native/core@latest skills add visual-
|
|
397
|
+
npx @agent-native/core@latest skills add visual-plans
|
|
398
398
|
\`\`\`
|
|
399
399
|
|
|
400
400
|
After that, \`/visual-plan\` and \`/visual-recap\` are the two installed slash
|
|
401
|
-
commands.
|
|
402
|
-
\`
|
|
403
|
-
|
|
404
|
-
|
|
401
|
+
commands. If you only need one command, use \`skills add visual-plan\` or
|
|
402
|
+
\`skills add visual-recap\` instead. The other planning modes
|
|
403
|
+
(\`create-ui-plan\`, \`create-prototype-plan\`, \`create-plan-design\`,
|
|
404
|
+
\`create-visual-questions\`) are MCP tools reachable from \`/visual-plan\`, not
|
|
405
|
+
separate slash commands. Pass \`--no-connect\` to register the connector without
|
|
406
|
+
authenticating, then run
|
|
405
407
|
\`npx @agent-native/core@latest connect https://plan.agent-native.com --client all\`
|
|
406
408
|
whenever you are ready, or choose a narrower \`--client\`. Auth and MCP tool
|
|
407
409
|
loading are per client config/session.
|
|
@@ -410,7 +412,7 @@ loading are per client config/session.
|
|
|
410
412
|
install with \`--mode local-files\`:
|
|
411
413
|
|
|
412
414
|
\`\`\`bash
|
|
413
|
-
npx @agent-native/core@latest skills add visual-
|
|
415
|
+
npx @agent-native/core@latest skills add visual-plans --mode local-files
|
|
414
416
|
\`\`\`
|
|
415
417
|
|
|
416
418
|
This mode does not register the Plan MCP connector. Before authoring structured
|
|
@@ -1648,14 +1650,16 @@ sign-in at setup — this is intended), so the first tool call in that client do
|
|
|
1648
1650
|
not hit an OAuth wall:
|
|
1649
1651
|
|
|
1650
1652
|
\`\`\`bash
|
|
1651
|
-
npx @agent-native/core@latest skills add visual-
|
|
1653
|
+
npx @agent-native/core@latest skills add visual-plans
|
|
1652
1654
|
\`\`\`
|
|
1653
1655
|
|
|
1654
1656
|
After that, \`/visual-plan\` and \`/visual-recap\` are the two installed slash
|
|
1655
|
-
commands.
|
|
1656
|
-
\`
|
|
1657
|
-
|
|
1658
|
-
|
|
1657
|
+
commands. If you only need one command, use \`skills add visual-plan\` or
|
|
1658
|
+
\`skills add visual-recap\` instead. The other planning modes
|
|
1659
|
+
(\`create-ui-plan\`, \`create-prototype-plan\`, \`create-plan-design\`,
|
|
1660
|
+
\`create-visual-questions\`) are MCP tools reachable from \`/visual-plan\`, not
|
|
1661
|
+
separate slash commands. Pass \`--no-connect\` to register the connector without
|
|
1662
|
+
authenticating, then run
|
|
1659
1663
|
\`npx @agent-native/core@latest connect https://plan.agent-native.com --client all\`
|
|
1660
1664
|
whenever you are ready, or choose a narrower \`--client\`. Auth and MCP tool
|
|
1661
1665
|
loading are per client config/session.
|
|
@@ -34,7 +34,10 @@ import {
|
|
|
34
34
|
} from "./workspace-core.js";
|
|
35
35
|
import { generateActionRegistryForProject } from "../vite/action-types-plugin.js";
|
|
36
36
|
import { mcpEmbedStaticAssetRouteRules } from "../shared/mcp-embed-headers.js";
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
AGENT_BACKGROUND_FUNCTION_NAME,
|
|
39
|
+
AGENT_CHAT_PROCESS_RUN_PATH,
|
|
40
|
+
} from "../agent/durable-background.js";
|
|
38
41
|
import {
|
|
39
42
|
AGENT_NATIVE_SOCIAL_IMAGE_ALT,
|
|
40
43
|
AGENT_NATIVE_SOCIAL_IMAGE_CACHE_BUSTER,
|
|
@@ -1502,31 +1505,52 @@ export function isDurableBackgroundDeployEnabled(): boolean {
|
|
|
1502
1505
|
}
|
|
1503
1506
|
|
|
1504
1507
|
/**
|
|
1505
|
-
* Single-template Netlify build: emit a
|
|
1506
|
-
*
|
|
1507
|
-
*
|
|
1508
|
-
*
|
|
1508
|
+
* Single-template Netlify build: emit a STANDALONE async background function so
|
|
1509
|
+
* the chat `_process-run` worker runs on Netlify's async (15-min) function
|
|
1510
|
+
* reached at its DIRECT url, instead of the synchronous `/*` catch-all.
|
|
1511
|
+
* Additive + flag-gated (see `isDurableBackgroundDeployEnabled`).
|
|
1512
|
+
*
|
|
1513
|
+
* Nitro's `netlify` preset emits a single synchronous function at
|
|
1514
|
+
* `.netlify/functions-internal/server` (`server.mjs` → `main.mjs`, `config.path`
|
|
1515
|
+
* `/*`). We copy that bundle to a STANDALONE function in the STANDARD functions
|
|
1516
|
+
* dir `.netlify/functions/server-agent-background` and write an entry that:
|
|
1517
|
+
* 1. Sets `globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true` at cold start
|
|
1518
|
+
* (read back by `isInBackgroundFunctionRuntime()` so the worker takes the
|
|
1519
|
+
* ~13-min soft-timeout). A `globalThis` flag — NOT `process.env` — keeps the
|
|
1520
|
+
* 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.
|
|
1509
1535
|
*
|
|
1510
|
-
*
|
|
1511
|
-
*
|
|
1512
|
-
*
|
|
1513
|
-
*
|
|
1536
|
+
* WHY this replaced the previous `config.path` approach: a function with a custom
|
|
1537
|
+
* `config.path` is reachable ONLY at that path (not at its default function url)
|
|
1538
|
+
* and `functions-internal` is not exposed at the default url at all. In prod,
|
|
1539
|
+
* Netlify routed `/_agent-native/agent-chat/_process-run` to the synchronous
|
|
1540
|
+
* Nitro `server` catch-all, NOT to the background function — verified live: a
|
|
1541
|
+
* POST to the process-run path returned a SYNCHRONOUS 401 from the handler
|
|
1542
|
+
* (the Nitro plugin ran) instead of a 202 async ack. A standalone function at
|
|
1543
|
+
* its direct url cannot be shadowed by the catch-all.
|
|
1514
1544
|
*
|
|
1515
|
-
*
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
1518
|
-
* function cannot be verified in this environment — Nitro owns the primary
|
|
1519
|
-
* function's routing manifest, and the precedence between the two functions for
|
|
1520
|
-
* that path is a Netlify runtime behavior. If routing resolves to the
|
|
1521
|
-
* synchronous function, the run still completes via the existing 40s
|
|
1522
|
-
* soft-timeout path (no durable win, no regression). See
|
|
1523
|
-
* docs/design/durable-agent-runs.md (Open risks #1).
|
|
1545
|
+
* Safety net regardless of Netlify routing nuance: if the direct-url dispatch
|
|
1546
|
+
* fast-fails (e.g. 404 when this function was not emitted), the foreground
|
|
1547
|
+
* handler degrades to an inline 40s synchronous run (see production-agent.ts).
|
|
1524
1548
|
*/
|
|
1525
1549
|
export function emitSingleTemplateNetlifyBackgroundFunction(
|
|
1526
1550
|
projectCwd: string,
|
|
1527
1551
|
): void {
|
|
1528
|
-
const
|
|
1529
|
-
const serverDir = path.join(
|
|
1552
|
+
const internalDir = path.join(projectCwd, ".netlify", "functions-internal");
|
|
1553
|
+
const serverDir = path.join(internalDir, "server");
|
|
1530
1554
|
if (!fs.existsSync(path.join(serverDir, "main.mjs"))) {
|
|
1531
1555
|
// Nitro output layout differs from what we expected — skip rather than
|
|
1532
1556
|
// guess. The single-function deploy is unaffected.
|
|
@@ -1536,24 +1560,69 @@ export function emitSingleTemplateNetlifyBackgroundFunction(
|
|
|
1536
1560
|
);
|
|
1537
1561
|
return;
|
|
1538
1562
|
}
|
|
1539
|
-
const backgroundName =
|
|
1563
|
+
const backgroundName = AGENT_BACKGROUND_FUNCTION_NAME;
|
|
1564
|
+
// Emit into the STANDARD functions dir (NOT functions-internal) so Netlify
|
|
1565
|
+
// exposes the function at its default url `/.netlify/functions/<name>`.
|
|
1566
|
+
const functionsDir = path.join(projectCwd, ".netlify", "functions");
|
|
1540
1567
|
const dest = path.join(functionsDir, backgroundName);
|
|
1568
|
+
fs.mkdirSync(functionsDir, { recursive: true });
|
|
1541
1569
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
1542
1570
|
copyDir(serverDir, dest);
|
|
1543
|
-
// Drop the original Nitro entry so our
|
|
1571
|
+
// Drop the original Nitro `/*` entry so our standalone entry is the entrypoint
|
|
1572
|
+
// and the copied bundle does NOT re-register the catch-all `config.path`.
|
|
1544
1573
|
fs.rmSync(path.join(dest, "server.mjs"), { force: true });
|
|
1545
1574
|
|
|
1546
|
-
const
|
|
1547
|
-
|
|
1548
|
-
|
|
1575
|
+
const processRunPath = JSON.stringify(AGENT_CHAT_PROCESS_RUN_PATH);
|
|
1576
|
+
const entry = `// Mark this isolate as the durable background runtime BEFORE the handler
|
|
1577
|
+
// bundle is imported, so isInBackgroundFunctionRuntime() reliably returns true
|
|
1578
|
+
// in this function. The deployed Lambda name is NOT guaranteed to end in
|
|
1579
|
+
// "-background" (Netlify may mangle/prefix it), so we cannot depend on
|
|
1580
|
+
// AWS_LAMBDA_FUNCTION_NAME alone. A globalThis flag (NOT process.env) avoids the
|
|
1581
|
+
// no-env-mutation guard and carries no cross-request state — it is a static,
|
|
1582
|
+
// set-once isolate marker read back by isInBackgroundFunctionRuntime().
|
|
1583
|
+
globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true;
|
|
1584
|
+
|
|
1585
|
+
// The framework route the Nitro router dispatches to (the _process-run plugin).
|
|
1586
|
+
const PROCESS_RUN_PATH = ${processRunPath};
|
|
1587
|
+
|
|
1588
|
+
let cachedHandler;
|
|
1589
|
+
|
|
1590
|
+
// Netlify v2 invokes this as (request, context). The Nitro netlify handler is a
|
|
1591
|
+
// Web-standard \`async (Request) => Response\` (see nitro/presets/netlify/runtime).
|
|
1592
|
+
// We are reached at this function's DIRECT url
|
|
1593
|
+
// (/.netlify/functions/${backgroundName}); rewrite the path to PROCESS_RUN_PATH
|
|
1594
|
+
// so Nitro routes it to the _process-run plugin, preserving method, ALL headers
|
|
1595
|
+
// (the HMAC Authorization: Bearer MUST survive — the plugin verifies it) and the
|
|
1596
|
+
// body. We clone the incoming Request with only its URL pathname rewritten;
|
|
1597
|
+
// query + origin are preserved.
|
|
1598
|
+
export default async function handler(request) {
|
|
1549
1599
|
cachedHandler ??= (await import("./main.mjs")).default;
|
|
1550
|
-
|
|
1600
|
+
const url = new URL(request.url);
|
|
1601
|
+
url.pathname = PROCESS_RUN_PATH;
|
|
1602
|
+
// Read the body once and pass it through. GET/HEAD have no body.
|
|
1603
|
+
const method = request.method || "POST";
|
|
1604
|
+
const hasBody = method !== "GET" && method !== "HEAD";
|
|
1605
|
+
const body = hasBody ? await request.text() : undefined;
|
|
1606
|
+
const rewritten = new Request(url.toString(), {
|
|
1607
|
+
method,
|
|
1608
|
+
headers: request.headers,
|
|
1609
|
+
body,
|
|
1610
|
+
});
|
|
1611
|
+
return cachedHandler(rewritten);
|
|
1551
1612
|
}
|
|
1552
1613
|
|
|
1553
1614
|
export const config = {
|
|
1554
1615
|
name: "agent background handler",
|
|
1555
1616
|
generator: "agent-native build",
|
|
1556
|
-
|
|
1617
|
+
// background: true makes Netlify invoke this ASYNCHRONOUSLY (immediate HTTP
|
|
1618
|
+
// 202 ack) with the 15-minute budget. NO custom \`path\`: that keeps the
|
|
1619
|
+
// function reachable at its DEFAULT url /.netlify/functions/${backgroundName},
|
|
1620
|
+
// which BYPASSES Nitro's /* catch-all. The previous version set a custom
|
|
1621
|
+
// \`config.path\` and Netlify routed the process-run path to the SYNCHRONOUS
|
|
1622
|
+
// catch-all instead (verified live: a synchronous 401 from the handler, not a
|
|
1623
|
+
// 202 async ack). See Netlify docs: build/functions/background-functions +
|
|
1624
|
+
// build/functions/configuration.
|
|
1625
|
+
background: true,
|
|
1557
1626
|
nodeBundler: "none",
|
|
1558
1627
|
includedFiles: ["**"],
|
|
1559
1628
|
preferStatic: false,
|
|
@@ -1561,9 +1630,10 @@ export const config = {
|
|
|
1561
1630
|
`;
|
|
1562
1631
|
fs.writeFileSync(path.join(dest, `${backgroundName}.mjs`), entry);
|
|
1563
1632
|
console.log(
|
|
1564
|
-
`[build] Emitted durable-background function "${backgroundName}" ` +
|
|
1565
|
-
`
|
|
1566
|
-
|
|
1633
|
+
`[build] Emitted standalone durable-background function "${backgroundName}" ` +
|
|
1634
|
+
`at /.netlify/functions/${backgroundName} (async, rewrites to ` +
|
|
1635
|
+
`${AGENT_CHAT_PROCESS_RUN_PATH}). REQUIRES real-deploy verification of ` +
|
|
1636
|
+
`Netlify async invocation — see docs/design/durable-agent-runs.md.`,
|
|
1567
1637
|
);
|
|
1568
1638
|
}
|
|
1569
1639
|
|
|
@@ -24,10 +24,6 @@ function seededResponse(
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function isEmptyConfig(config: Record<string, unknown>): boolean {
|
|
28
|
-
return !Array.isArray(config.panels) || config.panels.length === 0;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
27
|
export default defineAction({
|
|
32
28
|
description:
|
|
33
29
|
"Get a SQL analytics dashboard by ID, including its full panel config, visibility, and access metadata.",
|
|
@@ -78,10 +74,6 @@ export default defineAction({
|
|
|
78
74
|
});
|
|
79
75
|
}
|
|
80
76
|
const config = dash.config as Record<string, unknown>;
|
|
81
|
-
if (isEmptyConfig(config)) {
|
|
82
|
-
const seed = loadDashboardSeed(args.id);
|
|
83
|
-
if (seed) return seededResponse(args.id, seed);
|
|
84
|
-
}
|
|
85
77
|
return {
|
|
86
78
|
id: args.id,
|
|
87
79
|
...config,
|
|
@@ -466,7 +466,9 @@ export default defineAction({
|
|
|
466
466
|
.optional()
|
|
467
467
|
.describe("Replace the whole dashboard config (or a JSON string)."),
|
|
468
468
|
}),
|
|
469
|
-
|
|
469
|
+
// The SQL dashboard editor persists user edits through callAction(), which
|
|
470
|
+
// needs this action mounted under /_agent-native/actions/update-dashboard.
|
|
471
|
+
http: { method: "POST" },
|
|
470
472
|
mcpApp: {
|
|
471
473
|
compactCatalog: true,
|
|
472
474
|
resource: embedApp({
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
import { SqlHighlight } from "@/components/SqlHighlight";
|
|
4
|
+
|
|
5
|
+
export interface SqlEditorProps extends Omit<
|
|
6
|
+
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
|
|
7
|
+
"value"
|
|
8
|
+
> {
|
|
9
|
+
value: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const SqlEditor = React.forwardRef<HTMLTextAreaElement, SqlEditorProps>(
|
|
13
|
+
({ className, onScroll, value, disabled, readOnly, ...props }, ref) => {
|
|
14
|
+
const highlightRef = React.useRef<HTMLPreElement | null>(null);
|
|
15
|
+
|
|
16
|
+
const handleScroll = (event: React.UIEvent<HTMLTextAreaElement>) => {
|
|
17
|
+
const highlight = highlightRef.current;
|
|
18
|
+
if (highlight) {
|
|
19
|
+
highlight.scrollTop = event.currentTarget.scrollTop;
|
|
20
|
+
highlight.scrollLeft = event.currentTarget.scrollLeft;
|
|
21
|
+
}
|
|
22
|
+
onScroll?.(event);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
className={cn(
|
|
28
|
+
"relative min-h-[200px] rounded-md border border-input bg-background ring-offset-background focus-within:outline-none focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2",
|
|
29
|
+
disabled && "cursor-not-allowed opacity-50",
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
<SqlHighlight
|
|
34
|
+
ref={highlightRef}
|
|
35
|
+
aria-hidden="true"
|
|
36
|
+
sql={value || " "}
|
|
37
|
+
preClassName="pointer-events-none absolute inset-0 overflow-hidden rounded-md bg-transparent p-3 text-xs leading-5"
|
|
38
|
+
className="text-foreground"
|
|
39
|
+
/>
|
|
40
|
+
<textarea
|
|
41
|
+
ref={ref}
|
|
42
|
+
value={value}
|
|
43
|
+
disabled={disabled}
|
|
44
|
+
readOnly={readOnly}
|
|
45
|
+
onScroll={handleScroll}
|
|
46
|
+
spellCheck={false}
|
|
47
|
+
className={cn(
|
|
48
|
+
"relative z-10 min-h-[inherit] w-full resize-y rounded-md border-0 bg-transparent p-3 font-mono text-xs leading-5 text-transparent caret-foreground outline-none selection:bg-primary/30 placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed",
|
|
49
|
+
readOnly && "cursor-default",
|
|
50
|
+
)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
SqlEditor.displayName = "SqlEditor";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ReactNode } from "react";
|
|
1
|
+
import { forwardRef, type HTMLAttributes, type ReactNode } from "react";
|
|
2
2
|
import { cn } from "@/lib/utils";
|
|
3
3
|
|
|
4
4
|
const KEYWORDS = new Set([
|
|
@@ -266,36 +266,35 @@ const TOKEN_CLASS: Record<TokenType, string> = {
|
|
|
266
266
|
plain: "",
|
|
267
267
|
};
|
|
268
268
|
|
|
269
|
-
interface SqlHighlightProps {
|
|
269
|
+
interface SqlHighlightProps extends HTMLAttributes<HTMLPreElement> {
|
|
270
270
|
sql: string;
|
|
271
|
-
className?: string;
|
|
272
271
|
preClassName?: string;
|
|
273
272
|
}
|
|
274
273
|
|
|
275
|
-
export
|
|
276
|
-
sql,
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
274
|
+
export const SqlHighlight = forwardRef<HTMLPreElement, SqlHighlightProps>(
|
|
275
|
+
function SqlHighlight({ sql, className, preClassName, ...props }, ref) {
|
|
276
|
+
const tokens = tokenize(sql);
|
|
277
|
+
const nodes: ReactNode[] = tokens.map((tok, idx) => {
|
|
278
|
+
const cls = TOKEN_CLASS[tok.type];
|
|
279
|
+
if (!cls) return tok.value;
|
|
280
|
+
return (
|
|
281
|
+
<span key={idx} className={cls}>
|
|
282
|
+
{tok.value}
|
|
283
|
+
</span>
|
|
284
|
+
);
|
|
285
|
+
});
|
|
284
286
|
return (
|
|
285
|
-
<
|
|
286
|
-
{
|
|
287
|
-
|
|
287
|
+
<pre
|
|
288
|
+
ref={ref}
|
|
289
|
+
{...props}
|
|
290
|
+
className={cn(
|
|
291
|
+
"font-mono text-xs leading-5 whitespace-pre-wrap break-words",
|
|
292
|
+
preClassName,
|
|
293
|
+
className,
|
|
294
|
+
)}
|
|
295
|
+
>
|
|
296
|
+
<code>{nodes}</code>
|
|
297
|
+
</pre>
|
|
288
298
|
);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
<pre
|
|
292
|
-
className={cn(
|
|
293
|
-
"font-mono text-xs leading-5 whitespace-pre-wrap break-words",
|
|
294
|
-
preClassName,
|
|
295
|
-
className,
|
|
296
|
-
)}
|
|
297
|
-
>
|
|
298
|
-
<code>{nodes}</code>
|
|
299
|
-
</pre>
|
|
300
|
-
);
|
|
301
|
-
}
|
|
299
|
+
},
|
|
300
|
+
);
|