@agent-native/core 0.119.5 → 0.120.0
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 +2 -2
- package/corpus/core/CHANGELOG.md +18 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +1 -0
- package/corpus/core/src/cli/index.ts +64 -3
- package/corpus/core/src/deploy/build.ts +144 -2
- package/corpus/core/src/deploy/workspace-deploy.ts +123 -1
- package/corpus/core/src/integrations/integration-durable-dispatch-config.ts +22 -0
- package/corpus/core/src/integrations/integration-durable-dispatch.ts +190 -0
- package/corpus/core/src/integrations/pending-tasks-retry-job.ts +175 -87
- package/corpus/core/src/integrations/pending-tasks-store.ts +114 -11
- package/corpus/core/src/integrations/plugin.ts +113 -60
- package/corpus/core/src/integrations/task-queue-stats.ts +68 -15
- package/corpus/core/src/integrations/webhook-handler.ts +21 -46
- package/corpus/core/src/server/auth.ts +6 -0
- package/corpus/core/src/templates/default/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/corpus/core/src/templates/default/package.json +1 -1
- package/corpus/core/src/templates/headless/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/corpus/core/src/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/corpus/templates/assets/package.json +1 -1
- package/corpus/templates/brain/package.json +1 -1
- package/corpus/templates/calendar/package.json +1 -1
- package/corpus/templates/clips/package.json +1 -1
- package/corpus/templates/content/actions/add-database-item.ts +1 -0
- package/corpus/templates/content/actions/set-document-property.ts +0 -4
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +10 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +10 -4
- package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +5 -1
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +6 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +52 -33
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +16 -4
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +16 -7
- package/corpus/templates/content/app/hooks/use-create-page.ts +8 -3
- package/corpus/templates/content/app/hooks/use-document-properties.ts +1 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +3 -1
- package/corpus/templates/content/app/lib/optimistic-document.ts +29 -0
- package/corpus/templates/content/changelog/2026-07-21-editing-a-database-property-no-longer-refreshes-unrelated-co.md +6 -0
- package/corpus/templates/content/changelog/2026-07-23-new-database-ready.md +6 -0
- package/corpus/templates/content/package.json +1 -1
- package/corpus/templates/content/shared/api.ts +1 -0
- package/corpus/templates/design/package.json +1 -1
- package/corpus/templates/dispatch/changelog/2026-07-21-recover-stalled-slack-agent-work.md +6 -0
- package/corpus/templates/dispatch/package.json +1 -1
- package/corpus/templates/forms/package.json +1 -1
- package/corpus/templates/macros/package.json +1 -1
- package/corpus/templates/mail/package.json +1 -1
- package/corpus/templates/slides/package.json +1 -1
- package/dist/agent/durable-background.d.ts +1 -0
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +1 -0
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/cli/index.js +53 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +3 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +106 -3
- package/dist/deploy/build.js.map +1 -1
- package/dist/deploy/workspace-deploy.d.ts.map +1 -1
- package/dist/deploy/workspace-deploy.js +103 -2
- package/dist/deploy/workspace-deploy.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/integration-durable-dispatch-config.d.ts +7 -0
- package/dist/integrations/integration-durable-dispatch-config.d.ts.map +1 -0
- package/dist/integrations/integration-durable-dispatch-config.js +16 -0
- package/dist/integrations/integration-durable-dispatch-config.js.map +1 -0
- package/dist/integrations/integration-durable-dispatch.d.ts +23 -0
- package/dist/integrations/integration-durable-dispatch.d.ts.map +1 -0
- package/dist/integrations/integration-durable-dispatch.js +115 -0
- package/dist/integrations/integration-durable-dispatch.js.map +1 -0
- package/dist/integrations/pending-tasks-retry-job.d.ts +13 -1
- package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-retry-job.js +133 -74
- package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
- package/dist/integrations/pending-tasks-store.d.ts +14 -2
- package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-store.js +75 -11
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +88 -55
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/task-queue-stats.d.ts +15 -3
- package/dist/integrations/task-queue-stats.d.ts.map +1 -1
- package/dist/integrations/task-queue-stats.js +45 -15
- package/dist/integrations/task-queue-stats.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +18 -42
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/observability/routes.d.ts +6 -6
- package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +5 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/default/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/dist/templates/default/package.json +1 -1
- package/dist/templates/headless/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/dist/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/package.json +1 -1
- package/src/agent/durable-background.ts +1 -0
- package/src/cli/index.ts +64 -3
- package/src/deploy/build.ts +144 -2
- package/src/deploy/workspace-deploy.ts +123 -1
- package/src/integrations/integration-durable-dispatch-config.ts +22 -0
- package/src/integrations/integration-durable-dispatch.ts +190 -0
- package/src/integrations/pending-tasks-retry-job.ts +175 -87
- package/src/integrations/pending-tasks-store.ts +114 -11
- package/src/integrations/plugin.ts +113 -60
- package/src/integrations/task-queue-stats.ts +68 -15
- package/src/integrations/webhook-handler.ts +21 -46
- package/src/server/auth.ts +6 -0
- package/src/templates/default/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/src/templates/default/package.json +1 -1
- package/src/templates/headless/.agents/skills/integration-webhooks/SKILL.md +63 -22
- package/src/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +63 -22
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.120.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 20ebb96: `agent-native dev --inspect` (and `--inspect-brk`, optionally `=<port>`) now
|
|
8
|
+
attaches the Node inspector to **only** the Nitro API-server process, on a
|
|
9
|
+
single known port (default 9229). It selects Nitro's `node-process` dev runner
|
|
10
|
+
so the server is a real, attachable process, and injects `NODE_OPTIONS` through
|
|
11
|
+
a Vite preload that runs before Vite's own startup — so Vite, pnpm, and the CLI
|
|
12
|
+
are never inspected and there is exactly one debugger target. Set
|
|
13
|
+
`NITRO_DEV_RUNNER` yourself to override the runner.
|
|
14
|
+
|
|
15
|
+
## 0.119.6
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- f3c3523: Add an opt-in, scoped Netlify background handoff and scheduled recovery sweep for durable messaging-integration tasks.
|
|
20
|
+
|
|
3
21
|
## 0.119.5
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.120.0",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -85,6 +85,7 @@ export const AGENT_BACKGROUND_FUNCTION_URL_PATH = `/.netlify/functions/${AGENT_B
|
|
|
85
85
|
*/
|
|
86
86
|
export const AGENT_BACKGROUND_PROCESSOR_FIELD = "__agentNativeProcessor";
|
|
87
87
|
export const AGENT_BACKGROUND_PROCESSOR_A2A = "a2a";
|
|
88
|
+
export const AGENT_BACKGROUND_PROCESSOR_INTEGRATION = "integration";
|
|
88
89
|
export const AGENT_BACKGROUND_PROCESSOR_ROUTE = "route";
|
|
89
90
|
export const AGENT_BACKGROUND_PROCESSOR_ROUTE_FIELD =
|
|
90
91
|
"__agentNativeProcessorRoute";
|
|
@@ -276,6 +276,22 @@ function findViteBin(): string {
|
|
|
276
276
|
return findBinUpwards("vite") ?? "vite";
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
function findViteJsEntry(): string | null {
|
|
280
|
+
try {
|
|
281
|
+
const require = createRequire(path.join(process.cwd(), "package.json"));
|
|
282
|
+
const pkgJsonPath = require.resolve("vite/package.json");
|
|
283
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8")) as {
|
|
284
|
+
bin?: string | Record<string, string>;
|
|
285
|
+
};
|
|
286
|
+
const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.vite;
|
|
287
|
+
if (!rel) return null;
|
|
288
|
+
const entry = path.join(path.dirname(pkgJsonPath), rel);
|
|
289
|
+
return fs.existsSync(entry) ? entry : null;
|
|
290
|
+
} catch {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
279
295
|
function findTsxBin(): string {
|
|
280
296
|
const localTsx = path.resolve("node_modules/.bin/tsx");
|
|
281
297
|
if (fs.existsSync(localTsx)) return localTsx;
|
|
@@ -386,15 +402,31 @@ function isWorkspaceRoot(): boolean {
|
|
|
386
402
|
}
|
|
387
403
|
}
|
|
388
404
|
|
|
405
|
+
function extractNodeInspectFlag(args: string[]): {
|
|
406
|
+
inspectFlag: string | null;
|
|
407
|
+
rest: string[];
|
|
408
|
+
} {
|
|
409
|
+
const rest: string[] = [];
|
|
410
|
+
let inspectFlag: string | null = null;
|
|
411
|
+
for (const arg of args) {
|
|
412
|
+
if (/^--inspect(-brk)?(=.+)?$/.test(arg)) {
|
|
413
|
+
inspectFlag = arg;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
rest.push(arg);
|
|
417
|
+
}
|
|
418
|
+
return { inspectFlag, rest };
|
|
419
|
+
}
|
|
420
|
+
|
|
389
421
|
function run(
|
|
390
422
|
cmd: string,
|
|
391
423
|
cmdArgs: string[],
|
|
392
|
-
opts?: { stdio?: "inherit" | "pipe" },
|
|
424
|
+
opts?: { stdio?: "inherit" | "pipe"; env?: NodeJS.ProcessEnv },
|
|
393
425
|
) {
|
|
394
426
|
const child = spawn(cmd, cmdArgs, {
|
|
395
427
|
stdio: opts?.stdio ?? "inherit",
|
|
396
428
|
shell: process.platform === "win32",
|
|
397
|
-
env: process.env,
|
|
429
|
+
env: opts?.env ?? process.env,
|
|
398
430
|
});
|
|
399
431
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
400
432
|
// Forward signals to child so Cmd+C doesn't leave zombie processes holding ports
|
|
@@ -545,7 +577,36 @@ switch (command) {
|
|
|
545
577
|
break;
|
|
546
578
|
}
|
|
547
579
|
const vite = findViteBin();
|
|
548
|
-
|
|
580
|
+
const { inspectFlag, rest } = extractNodeInspectFlag(args);
|
|
581
|
+
if (!inspectFlag) {
|
|
582
|
+
run(vite, rest);
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
const viteJsEntry = findViteJsEntry();
|
|
586
|
+
if (!viteJsEntry) {
|
|
587
|
+
console.warn(
|
|
588
|
+
"[agent-native] Could not resolve Vite's JS entry; starting dev " +
|
|
589
|
+
"server without the debugger.",
|
|
590
|
+
);
|
|
591
|
+
run(vite, rest);
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
// Attach inspect flag to server process (not Vite or Nitro process)
|
|
595
|
+
const parsed = inspectFlag.match(/^--(inspect(?:-brk)?)(?:=(.+))?$/);
|
|
596
|
+
const kind = parsed?.[1] ?? "inspect";
|
|
597
|
+
const target = parsed?.[2] ?? "9229";
|
|
598
|
+
const directive = `--${kind}=${target}`;
|
|
599
|
+
const preload =
|
|
600
|
+
"data:text/javascript," +
|
|
601
|
+
encodeURIComponent(
|
|
602
|
+
`process.env.NODE_OPTIONS=((process.env.NODE_OPTIONS??"")+" ${directive}").trim();`,
|
|
603
|
+
);
|
|
604
|
+
const env = {
|
|
605
|
+
...process.env,
|
|
606
|
+
NITRO_DEV_RUNNER: process.env.NITRO_DEV_RUNNER ?? "node-process",
|
|
607
|
+
};
|
|
608
|
+
console.log(`[agent-native] API server debugger listening on ${target}`);
|
|
609
|
+
run(process.execPath, ["--import", preload, viteJsEntry, ...rest], { env });
|
|
549
610
|
break;
|
|
550
611
|
}
|
|
551
612
|
|
|
@@ -24,10 +24,16 @@ import {
|
|
|
24
24
|
AGENT_BACKGROUND_FUNCTION_NAME,
|
|
25
25
|
AGENT_BACKGROUND_PROCESSOR_A2A,
|
|
26
26
|
AGENT_BACKGROUND_PROCESSOR_FIELD,
|
|
27
|
+
AGENT_BACKGROUND_PROCESSOR_INTEGRATION,
|
|
27
28
|
AGENT_BACKGROUND_PROCESSOR_ROUTE,
|
|
28
29
|
AGENT_BACKGROUND_PROCESSOR_ROUTE_FIELD,
|
|
29
30
|
AGENT_CHAT_PROCESS_RUN_PATH,
|
|
30
31
|
} from "../agent/durable-background.js";
|
|
32
|
+
import {
|
|
33
|
+
INTEGRATION_RETRY_SWEEP_PATH,
|
|
34
|
+
INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT,
|
|
35
|
+
isIntegrationDurableDispatchConfigured,
|
|
36
|
+
} from "../integrations/integration-durable-dispatch-config.js";
|
|
31
37
|
import { normalizeAppBasePath } from "../server/app-base-path.js";
|
|
32
38
|
import {
|
|
33
39
|
DEFAULT_SSR_CDN_CACHE_CONTROL,
|
|
@@ -2369,6 +2375,13 @@ export function isDurableBackgroundDeployEnabled(): boolean {
|
|
|
2369
2375
|
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
2370
2376
|
}
|
|
2371
2377
|
|
|
2378
|
+
export const NETLIFY_INTEGRATION_RECOVERY_FUNCTION_NAME =
|
|
2379
|
+
"server-integration-recovery";
|
|
2380
|
+
|
|
2381
|
+
export function isIntegrationDurableDispatchDeployEnabled(): boolean {
|
|
2382
|
+
return isIntegrationDurableDispatchConfigured();
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2372
2385
|
const NETLIFY_KEEP_WARM_FUNCTION_NAME = "agent-native-keep-warm";
|
|
2373
2386
|
|
|
2374
2387
|
/**
|
|
@@ -2536,10 +2549,16 @@ export function emitSingleTemplateNetlifyBackgroundFunction(
|
|
|
2536
2549
|
|
|
2537
2550
|
const processRunPath = JSON.stringify(AGENT_CHAT_PROCESS_RUN_PATH);
|
|
2538
2551
|
const a2aProcessTaskPath = JSON.stringify("/_agent-native/a2a/_process-task");
|
|
2552
|
+
const integrationProcessTaskPath = JSON.stringify(
|
|
2553
|
+
"/_agent-native/integrations/process-task",
|
|
2554
|
+
);
|
|
2539
2555
|
const backgroundProcessorField = JSON.stringify(
|
|
2540
2556
|
AGENT_BACKGROUND_PROCESSOR_FIELD,
|
|
2541
2557
|
);
|
|
2542
2558
|
const backgroundProcessorA2A = JSON.stringify(AGENT_BACKGROUND_PROCESSOR_A2A);
|
|
2559
|
+
const backgroundProcessorIntegration = JSON.stringify(
|
|
2560
|
+
AGENT_BACKGROUND_PROCESSOR_INTEGRATION,
|
|
2561
|
+
);
|
|
2543
2562
|
const backgroundProcessorRoute = JSON.stringify(
|
|
2544
2563
|
AGENT_BACKGROUND_PROCESSOR_ROUTE,
|
|
2545
2564
|
);
|
|
@@ -2558,8 +2577,10 @@ globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true;
|
|
|
2558
2577
|
// The framework route the Nitro router dispatches to (the _process-run plugin).
|
|
2559
2578
|
const PROCESS_RUN_PATH = ${processRunPath};
|
|
2560
2579
|
const A2A_PROCESS_TASK_PATH = ${a2aProcessTaskPath};
|
|
2580
|
+
const INTEGRATION_PROCESS_TASK_PATH = ${integrationProcessTaskPath};
|
|
2561
2581
|
const BACKGROUND_PROCESSOR_FIELD = ${backgroundProcessorField};
|
|
2562
2582
|
const BACKGROUND_PROCESSOR_A2A = ${backgroundProcessorA2A};
|
|
2583
|
+
const BACKGROUND_PROCESSOR_INTEGRATION = ${backgroundProcessorIntegration};
|
|
2563
2584
|
const BACKGROUND_PROCESSOR_ROUTE = ${backgroundProcessorRoute};
|
|
2564
2585
|
const BACKGROUND_PROCESSOR_ROUTE_FIELD = ${backgroundProcessorRouteField};
|
|
2565
2586
|
|
|
@@ -2570,6 +2591,12 @@ function processorPathFromBody(body) {
|
|
|
2570
2591
|
if (parsed?.[BACKGROUND_PROCESSOR_FIELD] === BACKGROUND_PROCESSOR_A2A) {
|
|
2571
2592
|
return A2A_PROCESS_TASK_PATH;
|
|
2572
2593
|
}
|
|
2594
|
+
if (
|
|
2595
|
+
parsed?.[BACKGROUND_PROCESSOR_FIELD] ===
|
|
2596
|
+
BACKGROUND_PROCESSOR_INTEGRATION
|
|
2597
|
+
) {
|
|
2598
|
+
return INTEGRATION_PROCESS_TASK_PATH;
|
|
2599
|
+
}
|
|
2573
2600
|
const route = parsed?.[BACKGROUND_PROCESSOR_ROUTE_FIELD];
|
|
2574
2601
|
if (
|
|
2575
2602
|
parsed?.[BACKGROUND_PROCESSOR_FIELD] === BACKGROUND_PROCESSOR_ROUTE &&
|
|
@@ -2658,6 +2685,78 @@ export const config = {
|
|
|
2658
2685
|
);
|
|
2659
2686
|
}
|
|
2660
2687
|
|
|
2688
|
+
export function emitSingleTemplateNetlifyIntegrationRecoveryFunction(
|
|
2689
|
+
projectCwd: string,
|
|
2690
|
+
): void {
|
|
2691
|
+
const internalDir = path.join(projectCwd, ".netlify", "functions-internal");
|
|
2692
|
+
const serverDir = path.join(internalDir, "server");
|
|
2693
|
+
if (!fs.existsSync(path.join(serverDir, "main.mjs"))) {
|
|
2694
|
+
console.warn(
|
|
2695
|
+
"[build] Integration recovery emit skipped: expected Nitro Netlify function " +
|
|
2696
|
+
"at .netlify/functions-internal/server/main.mjs was not found.",
|
|
2697
|
+
);
|
|
2698
|
+
return;
|
|
2699
|
+
}
|
|
2700
|
+
const functionName = NETLIFY_INTEGRATION_RECOVERY_FUNCTION_NAME;
|
|
2701
|
+
const dest = path.join(internalDir, functionName);
|
|
2702
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
2703
|
+
copyDir(serverDir, dest);
|
|
2704
|
+
fs.rmSync(path.join(dest, "server.mjs"), { force: true });
|
|
2705
|
+
|
|
2706
|
+
const entry = `import { createHmac } from "node:crypto";
|
|
2707
|
+
|
|
2708
|
+
const SWEEP_PATH = ${JSON.stringify(INTEGRATION_RETRY_SWEEP_PATH)};
|
|
2709
|
+
const SWEEP_SUBJECT = ${JSON.stringify(INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT)};
|
|
2710
|
+
|
|
2711
|
+
function enabled() {
|
|
2712
|
+
const raw = process.env.AGENT_INTEGRATION_DURABLE_DISPATCH;
|
|
2713
|
+
if (!raw) return false;
|
|
2714
|
+
return ["1", "true", "yes", "on"].includes(raw.trim().toLowerCase());
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
function token(secret) {
|
|
2718
|
+
const timestamp = Date.now();
|
|
2719
|
+
const signature = createHmac("sha256", secret)
|
|
2720
|
+
.update(\`${INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT}:\${timestamp}\`)
|
|
2721
|
+
.digest("hex");
|
|
2722
|
+
return \`\${timestamp}.\${signature}\`;
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
let cachedHandler;
|
|
2726
|
+
|
|
2727
|
+
export default async function handler(request, context) {
|
|
2728
|
+
if (!enabled()) return new Response(null, { status: 204 });
|
|
2729
|
+
const secret = process.env.A2A_SECRET;
|
|
2730
|
+
if (!secret) {
|
|
2731
|
+
console.error("[integration-recovery] A2A_SECRET is required; sweep skipped");
|
|
2732
|
+
return new Response(null, { status: 204 });
|
|
2733
|
+
}
|
|
2734
|
+
cachedHandler ??= (await import("./main.mjs")).default;
|
|
2735
|
+
const url = new URL(request.url);
|
|
2736
|
+
url.pathname = SWEEP_PATH;
|
|
2737
|
+
const rewritten = new Request(url.toString(), {
|
|
2738
|
+
method: "POST",
|
|
2739
|
+
headers: {
|
|
2740
|
+
Authorization: \`Bearer \${token(secret)}\`,
|
|
2741
|
+
"Content-Type": "application/json",
|
|
2742
|
+
},
|
|
2743
|
+
body: JSON.stringify({ taskId: SWEEP_SUBJECT }),
|
|
2744
|
+
});
|
|
2745
|
+
return cachedHandler(rewritten, context);
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
export const config = {
|
|
2749
|
+
name: "integration pending-task recovery",
|
|
2750
|
+
generator: "agent-native build",
|
|
2751
|
+
schedule: "* * * * *",
|
|
2752
|
+
nodeBundler: "none",
|
|
2753
|
+
includedFiles: ["**"],
|
|
2754
|
+
preferStatic: false,
|
|
2755
|
+
};
|
|
2756
|
+
`;
|
|
2757
|
+
fs.writeFileSync(path.join(dest, `${functionName}.mjs`), entry);
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2661
2760
|
/**
|
|
2662
2761
|
* Nitro's Netlify preset can emit a harmful fallback rewrite to
|
|
2663
2762
|
* `/.netlify/functions/server`. With `config.path: "/*"`, that default URL is
|
|
@@ -2950,7 +3049,10 @@ export function assertSingleTemplateNetlifyBuildOutput(
|
|
|
2950
3049
|
);
|
|
2951
3050
|
}
|
|
2952
3051
|
|
|
2953
|
-
if (
|
|
3052
|
+
if (
|
|
3053
|
+
isDurableBackgroundDeployEnabled() ||
|
|
3054
|
+
isIntegrationDurableDispatchDeployEnabled()
|
|
3055
|
+
) {
|
|
2954
3056
|
const backgroundDir = path.join(
|
|
2955
3057
|
internalDir,
|
|
2956
3058
|
AGENT_BACKGROUND_FUNCTION_NAME,
|
|
@@ -2987,6 +3089,32 @@ export function assertSingleTemplateNetlifyBuildOutput(
|
|
|
2987
3089
|
}
|
|
2988
3090
|
}
|
|
2989
3091
|
|
|
3092
|
+
if (isIntegrationDurableDispatchDeployEnabled()) {
|
|
3093
|
+
const recoveryEntryPath = path.join(
|
|
3094
|
+
internalDir,
|
|
3095
|
+
NETLIFY_INTEGRATION_RECOVERY_FUNCTION_NAME,
|
|
3096
|
+
`${NETLIFY_INTEGRATION_RECOVERY_FUNCTION_NAME}.mjs`,
|
|
3097
|
+
);
|
|
3098
|
+
if (!fs.existsSync(recoveryEntryPath)) {
|
|
3099
|
+
failures.push(
|
|
3100
|
+
`integration durable dispatch is enabled but ${path.relative(
|
|
3101
|
+
projectCwd,
|
|
3102
|
+
recoveryEntryPath,
|
|
3103
|
+
)} was not emitted`,
|
|
3104
|
+
);
|
|
3105
|
+
} else {
|
|
3106
|
+
const recoveryEntry = fs.readFileSync(recoveryEntryPath, "utf-8");
|
|
3107
|
+
if (!/\bschedule\s*:\s*["']\* \* \* \* \*["']/.test(recoveryEntry)) {
|
|
3108
|
+
failures.push(
|
|
3109
|
+
`integration recovery entry ${path.relative(
|
|
3110
|
+
projectCwd,
|
|
3111
|
+
recoveryEntryPath,
|
|
3112
|
+
)} is missing the one-minute schedule`,
|
|
3113
|
+
);
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
|
|
2990
3118
|
if (failures.length > 0) {
|
|
2991
3119
|
throw new Error(
|
|
2992
3120
|
"[deploy] Netlify deploy guard failed; refusing to publish an output " +
|
|
@@ -3638,7 +3766,10 @@ export default bundle;
|
|
|
3638
3766
|
// bundle, so the chat `_process-run` POST lands on Netlify's async (15-min)
|
|
3639
3767
|
// function. When not opted in this is a no-op and the single-function
|
|
3640
3768
|
// deploy is byte-for-byte unchanged.
|
|
3641
|
-
if (
|
|
3769
|
+
if (
|
|
3770
|
+
isDurableBackgroundDeployEnabled() ||
|
|
3771
|
+
isIntegrationDurableDispatchDeployEnabled()
|
|
3772
|
+
) {
|
|
3642
3773
|
try {
|
|
3643
3774
|
emitSingleTemplateNetlifyBackgroundFunction(cwd);
|
|
3644
3775
|
} catch (err) {
|
|
@@ -3649,6 +3780,17 @@ export default bundle;
|
|
|
3649
3780
|
}
|
|
3650
3781
|
}
|
|
3651
3782
|
|
|
3783
|
+
if (isIntegrationDurableDispatchDeployEnabled()) {
|
|
3784
|
+
try {
|
|
3785
|
+
emitSingleTemplateNetlifyIntegrationRecoveryFunction(cwd);
|
|
3786
|
+
} catch (err) {
|
|
3787
|
+
console.warn(
|
|
3788
|
+
"[build] Failed to emit integration recovery Netlify function (non-fatal):",
|
|
3789
|
+
err instanceof Error ? err.message : err,
|
|
3790
|
+
);
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3652
3794
|
writeSingleTemplateNetlifyRedirects(cwd);
|
|
3653
3795
|
assertSingleTemplateNetlifyBuildOutput(cwd);
|
|
3654
3796
|
}
|
|
@@ -21,10 +21,16 @@ import path from "path";
|
|
|
21
21
|
import {
|
|
22
22
|
AGENT_BACKGROUND_PROCESSOR_A2A,
|
|
23
23
|
AGENT_BACKGROUND_PROCESSOR_FIELD,
|
|
24
|
+
AGENT_BACKGROUND_PROCESSOR_INTEGRATION,
|
|
24
25
|
AGENT_BACKGROUND_PROCESSOR_ROUTE,
|
|
25
26
|
AGENT_BACKGROUND_PROCESSOR_ROUTE_FIELD,
|
|
26
27
|
AGENT_CHAT_PROCESS_RUN_PATH,
|
|
27
28
|
} from "../agent/durable-background.js";
|
|
29
|
+
import {
|
|
30
|
+
INTEGRATION_RETRY_SWEEP_PATH,
|
|
31
|
+
INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT,
|
|
32
|
+
isIntegrationDurableDispatchConfigured,
|
|
33
|
+
} from "../integrations/integration-durable-dispatch-config.js";
|
|
28
34
|
import { findWorkspaceRoot } from "../scripts/utils.js";
|
|
29
35
|
import {
|
|
30
36
|
DEFAULT_WORKSPACE_APP_AUDIENCE,
|
|
@@ -677,9 +683,19 @@ function copyNetlifyFunctionIntoWorkspace(
|
|
|
677
683
|
// Durable background agent runs (default-ON; opt out with a falsy
|
|
678
684
|
// AGENT_CHAT_DURABLE_BACKGROUND). Additive ONLY: when explicitly opted out
|
|
679
685
|
// this emits nothing and the single-function deploy is unchanged.
|
|
680
|
-
|
|
686
|
+
const integrationDurableDispatch =
|
|
687
|
+
app === "dispatch" && isIntegrationDurableDispatchConfigured();
|
|
688
|
+
if (isDurableBackgroundDeployEnabled() || integrationDurableDispatch) {
|
|
681
689
|
emitNetlifyBackgroundFunction(workspaceRoot, app, src, workspaceApps);
|
|
682
690
|
}
|
|
691
|
+
if (integrationDurableDispatch) {
|
|
692
|
+
emitNetlifyIntegrationRecoveryFunction(
|
|
693
|
+
workspaceRoot,
|
|
694
|
+
app,
|
|
695
|
+
src,
|
|
696
|
+
workspaceApps,
|
|
697
|
+
);
|
|
698
|
+
}
|
|
683
699
|
}
|
|
684
700
|
|
|
685
701
|
/**
|
|
@@ -753,6 +769,7 @@ function emitNetlifyBackgroundFunction(
|
|
|
753
769
|
// incoming pathname to `/<app>/_agent-native/agent-chat/_process-run`.
|
|
754
770
|
const processRunPath = `${basePath}${AGENT_CHAT_PROCESS_RUN_PATH}`;
|
|
755
771
|
const a2aProcessTaskPath = `${basePath}/_agent-native/a2a/_process-task`;
|
|
772
|
+
const integrationProcessTaskPath = `${basePath}/_agent-native/integrations/process-task`;
|
|
756
773
|
const server = `// Mark this isolate as the durable background runtime BEFORE the handler bundle
|
|
757
774
|
// is imported, so isInBackgroundFunctionRuntime() reliably returns true in this
|
|
758
775
|
// function (the deployed Lambda name is not guaranteed to end in -background). A
|
|
@@ -764,8 +781,10 @@ const basePath = ${JSON.stringify(basePath)};
|
|
|
764
781
|
// The base-path-prefixed framework route the Nitro router dispatches to.
|
|
765
782
|
const PROCESS_RUN_PATH = ${JSON.stringify(processRunPath)};
|
|
766
783
|
const A2A_PROCESS_TASK_PATH = ${JSON.stringify(a2aProcessTaskPath)};
|
|
784
|
+
const INTEGRATION_PROCESS_TASK_PATH = ${JSON.stringify(integrationProcessTaskPath)};
|
|
767
785
|
const BACKGROUND_PROCESSOR_FIELD = ${JSON.stringify(AGENT_BACKGROUND_PROCESSOR_FIELD)};
|
|
768
786
|
const BACKGROUND_PROCESSOR_A2A = ${JSON.stringify(AGENT_BACKGROUND_PROCESSOR_A2A)};
|
|
787
|
+
const BACKGROUND_PROCESSOR_INTEGRATION = ${JSON.stringify(AGENT_BACKGROUND_PROCESSOR_INTEGRATION)};
|
|
769
788
|
const BACKGROUND_PROCESSOR_ROUTE = ${JSON.stringify(AGENT_BACKGROUND_PROCESSOR_ROUTE)};
|
|
770
789
|
const BACKGROUND_PROCESSOR_ROUTE_FIELD = ${JSON.stringify(AGENT_BACKGROUND_PROCESSOR_ROUTE_FIELD)};
|
|
771
790
|
|
|
@@ -776,6 +795,12 @@ function processorPathFromBody(body) {
|
|
|
776
795
|
if (parsed?.[BACKGROUND_PROCESSOR_FIELD] === BACKGROUND_PROCESSOR_A2A) {
|
|
777
796
|
return A2A_PROCESS_TASK_PATH;
|
|
778
797
|
}
|
|
798
|
+
if (
|
|
799
|
+
parsed?.[BACKGROUND_PROCESSOR_FIELD] ===
|
|
800
|
+
BACKGROUND_PROCESSOR_INTEGRATION
|
|
801
|
+
) {
|
|
802
|
+
return INTEGRATION_PROCESS_TASK_PATH;
|
|
803
|
+
}
|
|
779
804
|
const route = parsed?.[BACKGROUND_PROCESSOR_ROUTE_FIELD];
|
|
780
805
|
if (
|
|
781
806
|
parsed?.[BACKGROUND_PROCESSOR_FIELD] === BACKGROUND_PROCESSOR_ROUTE &&
|
|
@@ -862,6 +887,103 @@ export const config = {
|
|
|
862
887
|
);
|
|
863
888
|
}
|
|
864
889
|
|
|
890
|
+
function emitNetlifyIntegrationRecoveryFunction(
|
|
891
|
+
workspaceRoot: string,
|
|
892
|
+
app: string,
|
|
893
|
+
srcServerDir: string,
|
|
894
|
+
workspaceApps: WorkspaceAppManifestEntry[],
|
|
895
|
+
): void {
|
|
896
|
+
const functionName = `${app}-integration-recovery`;
|
|
897
|
+
const dest = path.join(netlifyFunctionsDir(workspaceRoot), functionName);
|
|
898
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
899
|
+
copyDir(srcServerDir, dest);
|
|
900
|
+
fs.rmSync(path.join(dest, "server.mjs"), { force: true });
|
|
901
|
+
|
|
902
|
+
const basePath = `/${app}`;
|
|
903
|
+
const workspaceAppAudience = workspaceAppAudienceForApp(workspaceApps, app);
|
|
904
|
+
const workspaceAppRouteAccess = workspaceAppRouteAccessForApp(
|
|
905
|
+
workspaceApps,
|
|
906
|
+
app,
|
|
907
|
+
);
|
|
908
|
+
const sweepPath = `${basePath}${INTEGRATION_RETRY_SWEEP_PATH}`;
|
|
909
|
+
const entry = `import { createHmac } from "node:crypto";
|
|
910
|
+
|
|
911
|
+
const basePath = ${JSON.stringify(basePath)};
|
|
912
|
+
const SWEEP_PATH = ${JSON.stringify(sweepPath)};
|
|
913
|
+
const SWEEP_SUBJECT = ${JSON.stringify(INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT)};
|
|
914
|
+
|
|
915
|
+
function setBasePathEnv() {
|
|
916
|
+
const processRef = globalThis.process ??= { env: {} };
|
|
917
|
+
processRef.env ??= {};
|
|
918
|
+
Object.assign(processRef.env, {
|
|
919
|
+
AGENT_NATIVE_WORKSPACE: "1",
|
|
920
|
+
AGENT_NATIVE_WORKSPACE_APP_ID: ${JSON.stringify(app)},
|
|
921
|
+
APP_BASE_PATH: basePath,
|
|
922
|
+
AGENT_NATIVE_WORKSPACE_APP_AUDIENCE: ${JSON.stringify(workspaceAppAudience)},
|
|
923
|
+
AGENT_NATIVE_WORKSPACE_APP_PUBLIC_PATHS: ${JSON.stringify(JSON.stringify(workspaceAppRouteAccess.publicPaths))},
|
|
924
|
+
AGENT_NATIVE_WORKSPACE_APP_PROTECTED_PATHS: ${JSON.stringify(JSON.stringify(workspaceAppRouteAccess.protectedPaths))},
|
|
925
|
+
VITE_AGENT_NATIVE_WORKSPACE: "1",
|
|
926
|
+
VITE_AGENT_NATIVE_WORKSPACE_APP_ID: ${JSON.stringify(app)},
|
|
927
|
+
VITE_APP_BASE_PATH: basePath,
|
|
928
|
+
VITE_AGENT_NATIVE_WORKSPACE_APP_AUDIENCE: ${JSON.stringify(workspaceAppAudience)},
|
|
929
|
+
VITE_AGENT_NATIVE_WORKSPACE_APP_PUBLIC_PATHS: ${JSON.stringify(JSON.stringify(workspaceAppRouteAccess.publicPaths))},
|
|
930
|
+
VITE_AGENT_NATIVE_WORKSPACE_APP_PROTECTED_PATHS: ${JSON.stringify(JSON.stringify(workspaceAppRouteAccess.protectedPaths))},
|
|
931
|
+
VITE_AGENT_NATIVE_WORKSPACE_APPS_JSON: ${JSON.stringify(JSON.stringify(workspaceApps))},
|
|
932
|
+
${JSON.stringify(WORKSPACE_APPS_ENV_KEY)}: ${JSON.stringify(JSON.stringify(workspaceApps))},
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function enabled() {
|
|
937
|
+
const raw = process.env.AGENT_INTEGRATION_DURABLE_DISPATCH;
|
|
938
|
+
if (!raw) return false;
|
|
939
|
+
return ["1", "true", "yes", "on"].includes(raw.trim().toLowerCase());
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function token(secret) {
|
|
943
|
+
const timestamp = Date.now();
|
|
944
|
+
const signature = createHmac("sha256", secret)
|
|
945
|
+
.update(\`${INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT}:\${timestamp}\`)
|
|
946
|
+
.digest("hex");
|
|
947
|
+
return \`\${timestamp}.\${signature}\`;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
setBasePathEnv();
|
|
951
|
+
let cachedHandler;
|
|
952
|
+
|
|
953
|
+
export default async function handler(request, context) {
|
|
954
|
+
setBasePathEnv();
|
|
955
|
+
if (!enabled()) return new Response(null, { status: 204 });
|
|
956
|
+
const secret = process.env.A2A_SECRET;
|
|
957
|
+
if (!secret) {
|
|
958
|
+
console.error("[integration-recovery] A2A_SECRET is required; sweep skipped");
|
|
959
|
+
return new Response(null, { status: 204 });
|
|
960
|
+
}
|
|
961
|
+
cachedHandler ??= (await import("./main.mjs")).default;
|
|
962
|
+
const url = new URL(request.url);
|
|
963
|
+
url.pathname = SWEEP_PATH;
|
|
964
|
+
const rewritten = new Request(url.toString(), {
|
|
965
|
+
method: "POST",
|
|
966
|
+
headers: {
|
|
967
|
+
Authorization: \`Bearer \${token(secret)}\`,
|
|
968
|
+
"Content-Type": "application/json",
|
|
969
|
+
},
|
|
970
|
+
body: JSON.stringify({ taskId: SWEEP_SUBJECT }),
|
|
971
|
+
});
|
|
972
|
+
return cachedHandler(rewritten, context);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
export const config = {
|
|
976
|
+
name: ${JSON.stringify(`${app} integration pending-task recovery`)},
|
|
977
|
+
generator: "agent-native workspace deploy",
|
|
978
|
+
schedule: "* * * * *",
|
|
979
|
+
nodeBundler: "none",
|
|
980
|
+
includedFiles: ["**"],
|
|
981
|
+
preferStatic: false,
|
|
982
|
+
};
|
|
983
|
+
`;
|
|
984
|
+
fs.writeFileSync(path.join(dest, `${functionName}.mjs`), entry);
|
|
985
|
+
}
|
|
986
|
+
|
|
865
987
|
function patchNetlifyFunctionEntry(
|
|
866
988
|
functionDir: string,
|
|
867
989
|
app: string,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const INTEGRATION_DURABLE_DISPATCH_ENV =
|
|
2
|
+
"AGENT_INTEGRATION_DURABLE_DISPATCH";
|
|
3
|
+
export const INTEGRATION_DURABLE_DISPATCH_SCOPES_ENV =
|
|
4
|
+
"AGENT_INTEGRATION_DURABLE_DISPATCH_SCOPES";
|
|
5
|
+
export const INTEGRATION_PROCESS_TASK_PATH =
|
|
6
|
+
"/_agent-native/integrations/process-task";
|
|
7
|
+
export const INTEGRATION_RETRY_SWEEP_PATH =
|
|
8
|
+
"/_agent-native/integrations/retry-stuck-tasks";
|
|
9
|
+
export const INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT =
|
|
10
|
+
"integration-pending-tasks-sweep";
|
|
11
|
+
|
|
12
|
+
export function isIntegrationDurableDispatchConfigured(): boolean {
|
|
13
|
+
const value = process.env.AGENT_INTEGRATION_DURABLE_DISPATCH;
|
|
14
|
+
if (!value) return false;
|
|
15
|
+
const normalized = value.trim().toLowerCase();
|
|
16
|
+
return (
|
|
17
|
+
normalized === "1" ||
|
|
18
|
+
normalized === "true" ||
|
|
19
|
+
normalized === "yes" ||
|
|
20
|
+
normalized === "on"
|
|
21
|
+
);
|
|
22
|
+
}
|