@agent-native/core 0.75.1 → 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 +30 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +56 -0
- package/corpus/core/src/agent/production-agent.ts +16 -3
- package/corpus/core/src/deploy/build.ts +87 -40
- 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/dist/agent/durable-background.d.ts +44 -0
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +51 -0
- 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/deploy/build.d.ts +37 -21
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +84 -40
- 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,35 @@
|
|
|
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
|
+
|
|
3
33
|
## 0.75.1
|
|
4
34
|
|
|
5
35
|
### Patch 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`).
|
|
@@ -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,
|
|
@@ -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,36 +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`).
|
|
1509
1512
|
*
|
|
1510
|
-
* Nitro's `netlify` preset emits a single function at
|
|
1511
|
-
* `.netlify/functions-internal/server` (`server.mjs` → `main.mjs`
|
|
1512
|
-
* that
|
|
1513
|
-
*
|
|
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.
|
|
1514
1535
|
*
|
|
1515
|
-
*
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
1518
|
-
*
|
|
1519
|
-
*
|
|
1520
|
-
*
|
|
1521
|
-
* (
|
|
1522
|
-
*
|
|
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.
|
|
1523
1544
|
*
|
|
1524
|
-
*
|
|
1525
|
-
*
|
|
1526
|
-
*
|
|
1527
|
-
* 40s soft-timeout path on the sync function (no durable win, no regression).
|
|
1528
|
-
* See 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).
|
|
1529
1548
|
*/
|
|
1530
1549
|
export function emitSingleTemplateNetlifyBackgroundFunction(
|
|
1531
1550
|
projectCwd: string,
|
|
1532
1551
|
): void {
|
|
1533
|
-
const
|
|
1534
|
-
const serverDir = path.join(
|
|
1552
|
+
const internalDir = path.join(projectCwd, ".netlify", "functions-internal");
|
|
1553
|
+
const serverDir = path.join(internalDir, "server");
|
|
1535
1554
|
if (!fs.existsSync(path.join(serverDir, "main.mjs"))) {
|
|
1536
1555
|
// Nitro output layout differs from what we expected — skip rather than
|
|
1537
1556
|
// guess. The single-function deploy is unaffected.
|
|
@@ -1541,13 +1560,19 @@ export function emitSingleTemplateNetlifyBackgroundFunction(
|
|
|
1541
1560
|
);
|
|
1542
1561
|
return;
|
|
1543
1562
|
}
|
|
1544
|
-
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");
|
|
1545
1567
|
const dest = path.join(functionsDir, backgroundName);
|
|
1568
|
+
fs.mkdirSync(functionsDir, { recursive: true });
|
|
1546
1569
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
1547
1570
|
copyDir(serverDir, dest);
|
|
1548
|
-
// 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`.
|
|
1549
1573
|
fs.rmSync(path.join(dest, "server.mjs"), { force: true });
|
|
1550
1574
|
|
|
1575
|
+
const processRunPath = JSON.stringify(AGENT_CHAT_PROCESS_RUN_PATH);
|
|
1551
1576
|
const entry = `// Mark this isolate as the durable background runtime BEFORE the handler
|
|
1552
1577
|
// bundle is imported, so isInBackgroundFunctionRuntime() reliably returns true
|
|
1553
1578
|
// in this function. The deployed Lambda name is NOT guaranteed to end in
|
|
@@ -1557,26 +1582,47 @@ export function emitSingleTemplateNetlifyBackgroundFunction(
|
|
|
1557
1582
|
// set-once isolate marker read back by isInBackgroundFunctionRuntime().
|
|
1558
1583
|
globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true;
|
|
1559
1584
|
|
|
1585
|
+
// The framework route the Nitro router dispatches to (the _process-run plugin).
|
|
1586
|
+
const PROCESS_RUN_PATH = ${processRunPath};
|
|
1587
|
+
|
|
1560
1588
|
let cachedHandler;
|
|
1561
1589
|
|
|
1562
|
-
|
|
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) {
|
|
1563
1599
|
cachedHandler ??= (await import("./main.mjs")).default;
|
|
1564
|
-
|
|
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);
|
|
1565
1612
|
}
|
|
1566
1613
|
|
|
1567
1614
|
export const config = {
|
|
1568
1615
|
name: "agent background handler",
|
|
1569
1616
|
generator: "agent-native build",
|
|
1570
|
-
// background: true
|
|
1571
|
-
//
|
|
1572
|
-
//
|
|
1573
|
-
//
|
|
1574
|
-
//
|
|
1575
|
-
//
|
|
1576
|
-
//
|
|
1577
|
-
// build/functions/
|
|
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.
|
|
1578
1625
|
background: true,
|
|
1579
|
-
path: ${JSON.stringify([AGENT_CHAT_PROCESS_RUN_PATH])},
|
|
1580
1626
|
nodeBundler: "none",
|
|
1581
1627
|
includedFiles: ["**"],
|
|
1582
1628
|
preferStatic: false,
|
|
@@ -1584,9 +1630,10 @@ export const config = {
|
|
|
1584
1630
|
`;
|
|
1585
1631
|
fs.writeFileSync(path.join(dest, `${backgroundName}.mjs`), entry);
|
|
1586
1632
|
console.log(
|
|
1587
|
-
`[build] Emitted durable-background function "${backgroundName}" ` +
|
|
1588
|
-
`
|
|
1589
|
-
|
|
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.`,
|
|
1590
1637
|
);
|
|
1591
1638
|
}
|
|
1592
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
|
+
);
|