@agent-native/core 0.74.0 → 0.75.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/core/CHANGELOG.md +28 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +36 -0
- package/corpus/core/src/agent/production-agent.ts +37 -4
- package/corpus/core/src/deploy/build.ts +21 -9
- package/corpus/core/src/deploy/workspace-deploy.ts +12 -6
- package/dist/agent/durable-background.d.ts +21 -0
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +33 -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 +36 -5
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/deploy/build.d.ts +13 -2
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +21 -9
- package/dist/deploy/build.js.map +1 -1
- package/dist/deploy/workspace-deploy.js +12 -6
- package/dist/deploy/workspace-deploy.js.map +1 -1
- package/package.json +1 -1
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.75.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1272755: Durable background agent-chat runs now complete cleanly instead of looping at
|
|
8
|
+
the 60s Netlify wall.
|
|
9
|
+
- The Netlify `-background` function (15-min async budget) is now emitted by
|
|
10
|
+
default. The deploy gates (`isDurableBackgroundDeployEnabled` in
|
|
11
|
+
`deploy/build.ts` and `deploy/workspace-deploy.ts`) are inverted to default-ON
|
|
12
|
+
to match the runtime gate — unset/empty means enabled; opt out with a falsy
|
|
13
|
+
`AGENT_CHAT_DURABLE_BACKGROUND` (`false`/`0`/`no`/`off`). This makes the chat
|
|
14
|
+
`_process-run` self-dispatch land on the real 15-min async function, so the
|
|
15
|
+
worker runs with its full budget (and likely fixes the app-specific dispatch
|
|
16
|
+
fast-fail, since the self-POST now hits an instant-202 async function).
|
|
17
|
+
- The run soft-timeout is now tied to the REAL function budget rather than
|
|
18
|
+
merely "I am the background worker." A new `isInBackgroundFunctionRuntime()`
|
|
19
|
+
guard (the Lambda function name ends in `-background`) gates the ~13-min
|
|
20
|
+
soft-timeout. A worker that lands on the regular ~60s function — or the
|
|
21
|
+
graceful inline fallback running in the foreground ~60s function — keeps the
|
|
22
|
+
~40s soft-timeout and checkpoints before the hard wall instead of overshooting
|
|
23
|
+
and re-dispatching in a loop.
|
|
24
|
+
- Server-driven continuation stays on for every durable worker so a run survives
|
|
25
|
+
the client disconnecting (closed tab): a worker on the regular ~60s function (a
|
|
26
|
+
Netlify routing miss, or a non-Netlify host with no `-background` function)
|
|
27
|
+
self-chains 40s chunks, while a worker in a real `-background` function chains
|
|
28
|
+
~13-min chunks. Only the per-chunk budget differs by function type; the
|
|
29
|
+
continuation is always server-driven.
|
|
30
|
+
|
|
3
31
|
## 0.74.0
|
|
4
32
|
|
|
5
33
|
### Minor Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -96,6 +96,42 @@ export function isHostedRuntimeForDurableBackground(): boolean {
|
|
|
96
96
|
);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* True when THIS process is actually executing inside a Netlify *background*
|
|
101
|
+
* function (the long, 15-min-budget async function whose deployed name ends in
|
|
102
|
+
* `-background`). Netlify runs functions on AWS Lambda and sets
|
|
103
|
+
* `AWS_LAMBDA_FUNCTION_NAME` to the function's name, so a `-background` suffix is
|
|
104
|
+
* the runtime proof that the ~60s synchronous wall does NOT apply here.
|
|
105
|
+
*
|
|
106
|
+
* This is the SAFETY GUARD for the soft-timeout regime. The `_process-run`
|
|
107
|
+
* self-dispatch worker (`isBackgroundWorker`) is NOT enough on its own: if the
|
|
108
|
+
* `-background` function was never emitted (deploy gate off, or Netlify routed
|
|
109
|
+
* the path to the synchronous function), the self-POST lands on the regular
|
|
110
|
+
* ~60s `server` function. A worker there MUST use the 40s soft-timeout and
|
|
111
|
+
* checkpoint before the 60s wall — using the ~13min budget would overshoot the
|
|
112
|
+
* hard wall and get killed at 60s, then re-dispatch/resume in a wasteful loop.
|
|
113
|
+
* So the 13-min budget is taken ONLY when this returns true.
|
|
114
|
+
*
|
|
115
|
+
* Falls back to the explicit `AGENT_CHAT_FORCE_BACKGROUND_RUNTIME` env (truthy)
|
|
116
|
+
* for hosts that don't expose a `-background`-suffixed function name but where
|
|
117
|
+
* the operator has confirmed a long async budget. Off by default.
|
|
118
|
+
*/
|
|
119
|
+
export function isInBackgroundFunctionRuntime(): boolean {
|
|
120
|
+
const lambdaName = process.env.AWS_LAMBDA_FUNCTION_NAME;
|
|
121
|
+
if (
|
|
122
|
+
typeof lambdaName === "string" &&
|
|
123
|
+
lambdaName.toLowerCase().endsWith("-background")
|
|
124
|
+
) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
const forced = process.env.AGENT_CHAT_FORCE_BACKGROUND_RUNTIME;
|
|
128
|
+
if (forced != null) {
|
|
129
|
+
const v = forced.trim().toLowerCase();
|
|
130
|
+
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
99
135
|
function isFlagEnabled(): boolean {
|
|
100
136
|
// Read the literal key (not `process.env[CONST]`) so guard:no-env-credentials
|
|
101
137
|
// can statically verify it against the allowlisted `AGENT_*` prefix. Keep this
|
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
AGENT_CHAT_PROCESS_RUN_PATH,
|
|
69
69
|
AGENT_CHAT_BACKGROUND_RUN_FIELD,
|
|
70
70
|
isAgentChatDurableBackgroundEnabled,
|
|
71
|
+
isInBackgroundFunctionRuntime,
|
|
71
72
|
} from "./durable-background.js";
|
|
72
73
|
import { fireInternalDispatch } from "../server/self-dispatch.js";
|
|
73
74
|
import { readBody } from "../server/h3-helpers.js";
|
|
@@ -3735,6 +3736,13 @@ export function createProductionAgentHandler(
|
|
|
3735
3736
|
? body[AGENT_CHAT_BACKGROUND_RUN_FIELD]!
|
|
3736
3737
|
: null;
|
|
3737
3738
|
const isBackgroundWorker = backgroundRunMarker !== null;
|
|
3739
|
+
// Whether this worker is REALLY executing inside a 15-min Netlify
|
|
3740
|
+
// `-background` function (proven by the runtime function name), not merely a
|
|
3741
|
+
// `_process-run` re-entry that may have landed on the ~60s synchronous
|
|
3742
|
+
// function. Only a true value unlocks the ~13-min soft-timeout budget; a
|
|
3743
|
+
// worker on the 60s function keeps the 40s clamp and checkpoints cleanly.
|
|
3744
|
+
const runsInBackgroundFunction =
|
|
3745
|
+
isBackgroundWorker && isInBackgroundFunctionRuntime();
|
|
3738
3746
|
// How many server-driven background continuations have already chained into
|
|
3739
3747
|
// this logical turn (0 on the first chunk). Used to bound the chain.
|
|
3740
3748
|
const backgroundContinuationCount =
|
|
@@ -4597,6 +4605,23 @@ export function createProductionAgentHandler(
|
|
|
4597
4605
|
// user-stopped runs do NOT chain.
|
|
4598
4606
|
if (
|
|
4599
4607
|
shouldChainBackgroundContinuation({
|
|
4608
|
+
// Self-chain server-side for EVERY durable worker, not only the
|
|
4609
|
+
// ones inside a `-background` function. Server-driven
|
|
4610
|
+
// continuation is the whole point of durable background: the run
|
|
4611
|
+
// must survive the client disconnecting (closed tab), so it
|
|
4612
|
+
// cannot depend on the browser re-POSTing `auto_continue`. A
|
|
4613
|
+
// worker on the regular ~60s function — a Netlify routing miss,
|
|
4614
|
+
// or a non-Netlify host (Vercel/Cloudflare/Render/Fly) that
|
|
4615
|
+
// never emits a `-background` function — checkpoints at the 40s
|
|
4616
|
+
// soft-timeout and self-dispatches the next 40s chunk; a worker
|
|
4617
|
+
// in a real `-background` function chains ~13-min chunks. Only
|
|
4618
|
+
// the per-chunk BUDGET differs by function type (gated by
|
|
4619
|
+
// `runsInBackgroundFunction` at the startRun call below); the
|
|
4620
|
+
// continuation itself must stay server-driven on both. (The
|
|
4621
|
+
// self-chain is only reachable when the initial dispatch already
|
|
4622
|
+
// succeeded — a dispatch fast-fail degrades to the inline
|
|
4623
|
+
// foreground fallback, which is not a worker and rides the
|
|
4624
|
+
// connected client's auto_continue instead.)
|
|
4600
4625
|
isBackgroundWorker,
|
|
4601
4626
|
run,
|
|
4602
4627
|
continuationCount: backgroundContinuationCount,
|
|
@@ -5055,10 +5080,18 @@ export function createProductionAgentHandler(
|
|
|
5055
5080
|
{
|
|
5056
5081
|
softTimeoutMs: options.runSoftTimeoutMs,
|
|
5057
5082
|
useHostedSoftTimeoutDefault: true,
|
|
5058
|
-
//
|
|
5059
|
-
//
|
|
5060
|
-
//
|
|
5061
|
-
|
|
5083
|
+
// Lift the soft-timeout clamp to ~13min ONLY when this run is actually
|
|
5084
|
+
// executing inside a real Netlify `-background` function (15-min budget,
|
|
5085
|
+
// no ~60s wall). Being the `_process-run` worker (`isBackgroundWorker`)
|
|
5086
|
+
// is NOT sufficient: if the `-background` function wasn't emitted, or
|
|
5087
|
+
// Netlify routed the self-POST to the synchronous function, the worker
|
|
5088
|
+
// landed on the regular ~60s `server` function — there it MUST keep the
|
|
5089
|
+
// 40s clamp and checkpoint before the wall, or it overshoots the 60s
|
|
5090
|
+
// hard kill and re-dispatches in a loop. `runsInBackgroundFunction`
|
|
5091
|
+
// gates the 13-min budget on the proven runtime, not merely on "I'm the
|
|
5092
|
+
// worker." Foreground runs never set this, so their 40s clamp is
|
|
5093
|
+
// unchanged.
|
|
5094
|
+
backgroundFunction: runsInBackgroundFunction,
|
|
5062
5095
|
// Fold continuation runs of one logical turn onto a single durable
|
|
5063
5096
|
// assistant message. Falls back to the runId (turn == run) when the
|
|
5064
5097
|
// client doesn't supply a turnId.
|
|
@@ -1480,14 +1480,25 @@ export function findInstalledResvgPackages(
|
|
|
1480
1480
|
/**
|
|
1481
1481
|
* Deploy-time gate for emitting the second `-background` Netlify function.
|
|
1482
1482
|
* Reads the same env flag the runtime gate uses
|
|
1483
|
-
* (`AGENT_CHAT_DURABLE_BACKGROUND`).
|
|
1484
|
-
*
|
|
1483
|
+
* (`AGENT_CHAT_DURABLE_BACKGROUND`).
|
|
1484
|
+
*
|
|
1485
|
+
* DEFAULT-ON, matching the runtime gate (`isFlagEnabled` in
|
|
1486
|
+
* durable-background.ts): unset/empty/unknown means enabled; an app opts OUT
|
|
1487
|
+
* only with an explicit falsy value (`false`/`0`/`no`/`off`). This is what
|
|
1488
|
+
* actually emits the 15-min `-background` function so the `_process-run`
|
|
1489
|
+
* dispatch lands on it (async 202 → the worker runs with the real 15-min budget
|
|
1490
|
+
* → its ~13-min soft-timeout fits). The deploy gate and runtime gate MUST agree:
|
|
1491
|
+
* if the deploy emitted no `-background` function but the runtime still routed
|
|
1492
|
+
* the worker into the ~13-min timeout regime, the worker would overshoot the
|
|
1493
|
+
* ~60s synchronous wall and re-dispatch in a loop. (The runtime now also guards
|
|
1494
|
+
* the ~13-min budget on the real function name via `isInBackgroundFunctionRuntime`,
|
|
1495
|
+
* so a missing emit degrades to clean 40s-chunked runs rather than the loop.)
|
|
1485
1496
|
*/
|
|
1486
1497
|
export function isDurableBackgroundDeployEnabled(): boolean {
|
|
1487
1498
|
const raw = process.env.AGENT_CHAT_DURABLE_BACKGROUND;
|
|
1488
|
-
if (raw == null) return
|
|
1499
|
+
if (raw == null) return true;
|
|
1489
1500
|
const v = raw.trim().toLowerCase();
|
|
1490
|
-
return v === "
|
|
1501
|
+
return !(v === "0" || v === "false" || v === "no" || v === "off");
|
|
1491
1502
|
}
|
|
1492
1503
|
|
|
1493
1504
|
/**
|
|
@@ -2018,11 +2029,12 @@ export default bundle;
|
|
|
2018
2029
|
copyInstalledFfmpegStaticPackage(nitro.options.output.serverDir);
|
|
2019
2030
|
}
|
|
2020
2031
|
|
|
2021
|
-
// Durable background agent runs (
|
|
2022
|
-
//
|
|
2023
|
-
//
|
|
2024
|
-
//
|
|
2025
|
-
//
|
|
2032
|
+
// Durable background agent runs (default-ON; opt out with a falsy
|
|
2033
|
+
// AGENT_CHAT_DURABLE_BACKGROUND). Additive ONLY: emits a SECOND Netlify
|
|
2034
|
+
// function whose name ends in `-background` re-exporting the same handler
|
|
2035
|
+
// bundle, so the chat `_process-run` POST lands on Netlify's async (15-min)
|
|
2036
|
+
// function. When explicitly opted out this is a no-op and the single-function
|
|
2037
|
+
// deploy is byte-for-byte unchanged.
|
|
2026
2038
|
if (preset === "netlify" && isDurableBackgroundDeployEnabled()) {
|
|
2027
2039
|
try {
|
|
2028
2040
|
emitSingleTemplateNetlifyBackgroundFunction(cwd);
|
|
@@ -667,8 +667,9 @@ function copyNetlifyFunctionIntoWorkspace(
|
|
|
667
667
|
copyDir(src, dest);
|
|
668
668
|
patchNetlifyFunctionEntry(dest, app, workspaceApps, staticDir);
|
|
669
669
|
|
|
670
|
-
// Durable background agent runs (
|
|
671
|
-
//
|
|
670
|
+
// Durable background agent runs (default-ON; opt out with a falsy
|
|
671
|
+
// AGENT_CHAT_DURABLE_BACKGROUND). Additive ONLY: when explicitly opted out
|
|
672
|
+
// this emits nothing and the single-function deploy is unchanged.
|
|
672
673
|
if (isDurableBackgroundDeployEnabled()) {
|
|
673
674
|
emitNetlifyBackgroundFunction(workspaceRoot, app, src, workspaceApps);
|
|
674
675
|
}
|
|
@@ -677,14 +678,19 @@ function copyNetlifyFunctionIntoWorkspace(
|
|
|
677
678
|
/**
|
|
678
679
|
* Deploy-time gate for emitting the second `-background` Netlify function. Reads
|
|
679
680
|
* the same env flag the runtime gate uses (`AGENT_CHAT_DURABLE_BACKGROUND`).
|
|
680
|
-
*
|
|
681
|
-
* (
|
|
681
|
+
*
|
|
682
|
+
* DEFAULT-ON, matching the runtime gate (`isFlagEnabled` in
|
|
683
|
+
* durable-background.ts) and the single-template gate
|
|
684
|
+
* (`isDurableBackgroundDeployEnabled` in deploy/build.ts): unset/empty/unknown
|
|
685
|
+
* means enabled; an app opts OUT only with an explicit falsy value
|
|
686
|
+
* (`false`/`0`/`no`/`off`). This emits the per-app 15-min `-background` function
|
|
687
|
+
* so the chat `_process-run` dispatch lands on it with the real long budget.
|
|
682
688
|
*/
|
|
683
689
|
function isDurableBackgroundDeployEnabled(): boolean {
|
|
684
690
|
const raw = process.env.AGENT_CHAT_DURABLE_BACKGROUND;
|
|
685
|
-
if (raw == null) return
|
|
691
|
+
if (raw == null) return true;
|
|
686
692
|
const v = raw.trim().toLowerCase();
|
|
687
|
-
return v === "
|
|
693
|
+
return !(v === "0" || v === "false" || v === "no" || v === "off");
|
|
688
694
|
}
|
|
689
695
|
|
|
690
696
|
/**
|
|
@@ -23,6 +23,27 @@ export declare const AGENT_CHAT_BACKGROUND_RUN_FIELD = "__backgroundRun";
|
|
|
23
23
|
* what "hosted" means.
|
|
24
24
|
*/
|
|
25
25
|
export declare function isHostedRuntimeForDurableBackground(): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* True when THIS process is actually executing inside a Netlify *background*
|
|
28
|
+
* function (the long, 15-min-budget async function whose deployed name ends in
|
|
29
|
+
* `-background`). Netlify runs functions on AWS Lambda and sets
|
|
30
|
+
* `AWS_LAMBDA_FUNCTION_NAME` to the function's name, so a `-background` suffix is
|
|
31
|
+
* the runtime proof that the ~60s synchronous wall does NOT apply here.
|
|
32
|
+
*
|
|
33
|
+
* This is the SAFETY GUARD for the soft-timeout regime. The `_process-run`
|
|
34
|
+
* self-dispatch worker (`isBackgroundWorker`) is NOT enough on its own: if the
|
|
35
|
+
* `-background` function was never emitted (deploy gate off, or Netlify routed
|
|
36
|
+
* the path to the synchronous function), the self-POST lands on the regular
|
|
37
|
+
* ~60s `server` function. A worker there MUST use the 40s soft-timeout and
|
|
38
|
+
* checkpoint before the 60s wall — using the ~13min budget would overshoot the
|
|
39
|
+
* hard wall and get killed at 60s, then re-dispatch/resume in a wasteful loop.
|
|
40
|
+
* So the 13-min budget is taken ONLY when this returns true.
|
|
41
|
+
*
|
|
42
|
+
* Falls back to the explicit `AGENT_CHAT_FORCE_BACKGROUND_RUNTIME` env (truthy)
|
|
43
|
+
* for hosts that don't expose a `-background`-suffixed function name but where
|
|
44
|
+
* the operator has confirmed a long async budget. Off by default.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isInBackgroundFunctionRuntime(): boolean;
|
|
26
47
|
/**
|
|
27
48
|
* The single gate. True when the flag is not explicitly disabled (default-on)
|
|
28
49
|
* AND the runtime is hosted AND A2A_SECRET is configured. False otherwise — and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"durable-background.d.ts","sourceRoot":"","sources":["../../src/agent/durable-background.ts"],"names":[],"mappings":"AA6CA;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,2CACE,CAAC;AAE3C;;;GAGG;AACH,eAAO,MAAM,iCAAiC,kCACb,CAAC;AAElC;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,oBAAoB,CAAC;AAEjE;;;;GAIG;AACH,wBAAgB,mCAAmC,IAAI,OAAO,CAsB7D;AAyBD;;;;;;GAMG;AACH,wBAAgB,mCAAmC,IAAI,OAAO,CAM7D;AAED,uDAAuD;AACvD,MAAM,MAAM,qBAAqB,GAC7B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,qBAAqB,CA0CvB"}
|
|
1
|
+
{"version":3,"file":"durable-background.d.ts","sourceRoot":"","sources":["../../src/agent/durable-background.ts"],"names":[],"mappings":"AA6CA;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,2CACE,CAAC;AAE3C;;;GAGG;AACH,eAAO,MAAM,iCAAiC,kCACb,CAAC;AAElC;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,oBAAoB,CAAC;AAEjE;;;;GAIG;AACH,wBAAgB,mCAAmC,IAAI,OAAO,CAsB7D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,6BAA6B,IAAI,OAAO,CAcvD;AAyBD;;;;;;GAMG;AACH,wBAAgB,mCAAmC,IAAI,OAAO,CAM7D;AAED,uDAAuD;AACvD,MAAM,MAAM,qBAAqB,GAC7B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,qBAAqB,CA0CvB"}
|
|
@@ -77,6 +77,39 @@ export function isHostedRuntimeForDurableBackground() {
|
|
|
77
77
|
process.env.FLY_APP_NAME ||
|
|
78
78
|
process.env.K_SERVICE);
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* True when THIS process is actually executing inside a Netlify *background*
|
|
82
|
+
* function (the long, 15-min-budget async function whose deployed name ends in
|
|
83
|
+
* `-background`). Netlify runs functions on AWS Lambda and sets
|
|
84
|
+
* `AWS_LAMBDA_FUNCTION_NAME` to the function's name, so a `-background` suffix is
|
|
85
|
+
* the runtime proof that the ~60s synchronous wall does NOT apply here.
|
|
86
|
+
*
|
|
87
|
+
* This is the SAFETY GUARD for the soft-timeout regime. The `_process-run`
|
|
88
|
+
* self-dispatch worker (`isBackgroundWorker`) is NOT enough on its own: if the
|
|
89
|
+
* `-background` function was never emitted (deploy gate off, or Netlify routed
|
|
90
|
+
* the path to the synchronous function), the self-POST lands on the regular
|
|
91
|
+
* ~60s `server` function. A worker there MUST use the 40s soft-timeout and
|
|
92
|
+
* checkpoint before the 60s wall — using the ~13min budget would overshoot the
|
|
93
|
+
* hard wall and get killed at 60s, then re-dispatch/resume in a wasteful loop.
|
|
94
|
+
* So the 13-min budget is taken ONLY when this returns true.
|
|
95
|
+
*
|
|
96
|
+
* Falls back to the explicit `AGENT_CHAT_FORCE_BACKGROUND_RUNTIME` env (truthy)
|
|
97
|
+
* for hosts that don't expose a `-background`-suffixed function name but where
|
|
98
|
+
* the operator has confirmed a long async budget. Off by default.
|
|
99
|
+
*/
|
|
100
|
+
export function isInBackgroundFunctionRuntime() {
|
|
101
|
+
const lambdaName = process.env.AWS_LAMBDA_FUNCTION_NAME;
|
|
102
|
+
if (typeof lambdaName === "string" &&
|
|
103
|
+
lambdaName.toLowerCase().endsWith("-background")) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
const forced = process.env.AGENT_CHAT_FORCE_BACKGROUND_RUNTIME;
|
|
107
|
+
if (forced != null) {
|
|
108
|
+
const v = forced.trim().toLowerCase();
|
|
109
|
+
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
80
113
|
function isFlagEnabled() {
|
|
81
114
|
// Read the literal key (not `process.env[CONST]`) so guard:no-env-credentials
|
|
82
115
|
// can statically verify it against the allowlisted `AGENT_*` prefix. Keep this
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"durable-background.js","sourceRoot":"","sources":["../../src/agent/durable-background.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Durable background agent-chat runs (Netlify background functions).\n *\n * Off by default. When enabled, a long in-app agent-chat turn is dispatched\n * into a Netlify *background* function (15-min budget) instead of completing\n * synchronously under the ~40s soft-timeout. The foreground POST claims the\n * run slot, inserts the run row, fires an HMAC-signed self-dispatch to\n * `AGENT_CHAT_PROCESS_RUN_PATH`, and returns the existing SSE subscription so\n * the client streams the same events (via the cross-isolate SQL-poll path)\n * with no client change.\n *\n * This module owns ONLY the gating decision + shared constants so both the\n * HTTP handler (`production-agent.ts`) and the processor route\n * (`agent-chat-plugin.ts`) agree on when the path is active without a circular\n * import. The actual run machinery is reused verbatim from run-manager /\n * run-store / self-dispatch / internal-token.\n *\n * GUARDRAIL: when `isAgentChatDurableBackgroundEnabled()` returns false, the\n * agent-chat handler must behave byte-for-byte like the current synchronous\n * path. The gate is true only when ALL of these hold:\n * 1. `AGENT_CHAT_DURABLE_BACKGROUND` env is not explicitly disabled. It is\n * DEFAULT-ON: unset/empty/unknown counts as enabled; set it to a falsy\n * value (`false`/`0`/`no`/`off`) to opt a specific app back out.\n * 2. The runtime is hosted/serverless (local dev keeps the inline path so SSE\n * stays a single live stream and no second function is needed).\n * 3. `A2A_SECRET` is configured (the HMAC handoff is required to authenticate\n * the background dispatch; without it the dispatch can't be trusted).\n *\n * Default-on is safe because a *dispatch failure degrades to an inline run*: if\n * the self-dispatch self-POST can't be delivered (fast connection error or\n * fast non-2xx), the foreground handler runs the turn synchronously instead of\n * erroring (see `production-agent.ts` — the inline fallback claims the run row\n * atomically so a delayed delivery can never double-execute). So an app where\n * durable dispatch happens to fail still gets a working chat, just without the\n * 15-min budget.\n */\nimport {\n hasConfiguredA2ASecret,\n isA2AProductionRuntime,\n} from \"../a2a/auth-policy.js\";\nimport {\n extractBearerToken,\n verifyInternalToken,\n} from \"../integrations/internal-token.js\";\n\n/**\n * Framework route the background function actually runs — sibling to\n * `AGENT_TEAM_PROCESS_RUN_PATH`. Reached *through* the Netlify background\n * function, so it inherits the 15-min budget.\n */\nexport const AGENT_CHAT_PROCESS_RUN_PATH =\n \"/_agent-native/agent-chat/_process-run\";\n\n/**\n * Env flag for durable background runs. DEFAULT-ON: unset means enabled; an app\n * opts OUT with an explicit falsy value (`false`/`0`/`no`/`off`).\n */\nexport const AGENT_CHAT_DURABLE_BACKGROUND_ENV =\n \"AGENT_CHAT_DURABLE_BACKGROUND\";\n\n/**\n * Body field the foreground handler injects when self-dispatching to the\n * background processor. Its presence is how the re-entered handler knows it is\n * the background worker (run inline with the background soft-timeout; do NOT\n * re-claim the slot or re-dispatch). Untrusted on its own — the route also\n * verifies the HMAC token before invoking the handler.\n */\nexport const AGENT_CHAT_BACKGROUND_RUN_FIELD = \"__backgroundRun\";\n\n/**\n * Mirror of run-manager's private `isHostedRuntime`. Kept in sync deliberately:\n * the durable-background gate must agree with the soft-timeout regime about\n * what \"hosted\" means.\n */\nexport function isHostedRuntimeForDurableBackground(): boolean {\n if (\n process.env.NETLIFY &&\n process.env.NETLIFY !== \"false\" &&\n process.env.NETLIFY_LOCAL !== \"true\"\n ) {\n return true;\n }\n if (\n process.env.AWS_LAMBDA_FUNCTION_NAME &&\n process.env.NETLIFY_LOCAL !== \"true\"\n ) {\n return true;\n }\n return Boolean(\n process.env.CF_PAGES ||\n process.env.VERCEL ||\n process.env.VERCEL_ENV ||\n process.env.RENDER ||\n process.env.FLY_APP_NAME ||\n process.env.K_SERVICE,\n );\n}\n\nfunction isFlagEnabled(): boolean {\n // Read the literal key (not `process.env[CONST]`) so guard:no-env-credentials\n // can statically verify it against the allowlisted `AGENT_*` prefix. Keep this\n // in sync with AGENT_CHAT_DURABLE_BACKGROUND_ENV.\n //\n // DEFAULT-ON: durable background runs are the desired behavior for every\n // hosted app. So an unset/empty/unknown flag means ON; an app opts OUT only\n // with an explicit falsy value. This still composes with the hosted +\n // A2A_SECRET gates below, so non-hosted / unconfigured apps stay synchronous.\n // Safety net: a failed dispatch degrades to a synchronous inline run (see\n // production-agent.ts), so default-on cannot break chat even if the\n // self-dispatch can't be delivered on a given app.\n const raw = process.env.AGENT_CHAT_DURABLE_BACKGROUND;\n if (raw == null) return true;\n const normalized = raw.trim().toLowerCase();\n return !(\n normalized === \"0\" ||\n normalized === \"false\" ||\n normalized === \"no\" ||\n normalized === \"off\"\n );\n}\n\n/**\n * The single gate. True when the flag is not explicitly disabled (default-on)\n * AND the runtime is hosted AND A2A_SECRET is configured. False otherwise — and\n * false means the current synchronous behavior is used, unchanged. So a local /\n * non-hosted / unconfigured app stays synchronous even with the flag defaulting\n * on; durable only engages where the runtime actually supports it.\n */\nexport function isAgentChatDurableBackgroundEnabled(): boolean {\n return (\n isFlagEnabled() &&\n isHostedRuntimeForDurableBackground() &&\n hasConfiguredA2ASecret()\n );\n}\n\n/** Decision returned by `prepareProcessRunRequest`. */\nexport type ProcessRunPreparation =\n | {\n ok: true;\n /** The pre-claimed run id the background worker must reuse. */\n runId: string;\n /** Body to stash for the re-entered handler (marker guaranteed present). */\n body: Record<string, unknown>;\n }\n | {\n ok: false;\n /** HTTP status the route should return. */\n status: number;\n /** Error payload. */\n error: string;\n };\n\n/**\n * Pure, transport-agnostic core of the `_process-run` route: validate the body,\n * authenticate the HMAC self-dispatch, and produce the body the re-entered\n * agent-chat handler should run as the background worker.\n *\n * Auth policy mirrors the agent-teams processor exactly:\n * - `A2A_SECRET` set → require a valid `verifyInternalToken(runId, token)`.\n * - no secret but a production runtime → refuse (503) — never run unsigned in\n * prod.\n * - no secret + non-prod (local dev) → allow unsigned; the SQL atomic claim\n * in the worker still prevents double-processing.\n *\n * Extracted from the route handler so the auth + marker-prep decision is unit\n * testable without booting the whole Nitro plugin. The route only adds body\n * reading and the final handler invocation around this.\n */\nexport function prepareProcessRunRequest(\n body: unknown,\n authHeader: string | undefined,\n): ProcessRunPreparation {\n if (!body || typeof body !== \"object\") {\n return { ok: false, status: 400, error: \"Invalid request body\" };\n }\n const record = body as Record<string, unknown>;\n const marker = record[AGENT_CHAT_BACKGROUND_RUN_FIELD] as\n | { runId?: unknown }\n | undefined;\n const runId =\n marker && typeof marker.runId === \"string\"\n ? marker.runId\n : typeof record.taskId === \"string\"\n ? (record.taskId as string)\n : \"\";\n if (!runId) {\n return { ok: false, status: 400, error: \"runId required\" };\n }\n\n if (hasConfiguredA2ASecret()) {\n const token = extractBearerToken(authHeader);\n if (!verifyInternalToken(runId, token ?? \"\")) {\n return {\n ok: false,\n status: 401,\n error: \"Invalid or expired processor token\",\n };\n }\n } else if (isA2AProductionRuntime()) {\n return {\n ok: false,\n status: 503,\n error:\n \"Agent chat background processor not configured — set A2A_SECRET on this deployment.\",\n };\n }\n\n // Ensure the marker is present so the re-entered handler runs as the\n // background worker (reuses runId/turnId, no re-claim, no re-dispatch).\n if (!marker || typeof marker.runId !== \"string\") {\n record[AGENT_CHAT_BACKGROUND_RUN_FIELD] = { runId };\n }\n return { ok: true, runId, body: record };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"durable-background.js","sourceRoot":"","sources":["../../src/agent/durable-background.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Durable background agent-chat runs (Netlify background functions).\n *\n * Off by default. When enabled, a long in-app agent-chat turn is dispatched\n * into a Netlify *background* function (15-min budget) instead of completing\n * synchronously under the ~40s soft-timeout. The foreground POST claims the\n * run slot, inserts the run row, fires an HMAC-signed self-dispatch to\n * `AGENT_CHAT_PROCESS_RUN_PATH`, and returns the existing SSE subscription so\n * the client streams the same events (via the cross-isolate SQL-poll path)\n * with no client change.\n *\n * This module owns ONLY the gating decision + shared constants so both the\n * HTTP handler (`production-agent.ts`) and the processor route\n * (`agent-chat-plugin.ts`) agree on when the path is active without a circular\n * import. The actual run machinery is reused verbatim from run-manager /\n * run-store / self-dispatch / internal-token.\n *\n * GUARDRAIL: when `isAgentChatDurableBackgroundEnabled()` returns false, the\n * agent-chat handler must behave byte-for-byte like the current synchronous\n * path. The gate is true only when ALL of these hold:\n * 1. `AGENT_CHAT_DURABLE_BACKGROUND` env is not explicitly disabled. It is\n * DEFAULT-ON: unset/empty/unknown counts as enabled; set it to a falsy\n * value (`false`/`0`/`no`/`off`) to opt a specific app back out.\n * 2. The runtime is hosted/serverless (local dev keeps the inline path so SSE\n * stays a single live stream and no second function is needed).\n * 3. `A2A_SECRET` is configured (the HMAC handoff is required to authenticate\n * the background dispatch; without it the dispatch can't be trusted).\n *\n * Default-on is safe because a *dispatch failure degrades to an inline run*: if\n * the self-dispatch self-POST can't be delivered (fast connection error or\n * fast non-2xx), the foreground handler runs the turn synchronously instead of\n * erroring (see `production-agent.ts` — the inline fallback claims the run row\n * atomically so a delayed delivery can never double-execute). So an app where\n * durable dispatch happens to fail still gets a working chat, just without the\n * 15-min budget.\n */\nimport {\n hasConfiguredA2ASecret,\n isA2AProductionRuntime,\n} from \"../a2a/auth-policy.js\";\nimport {\n extractBearerToken,\n verifyInternalToken,\n} from \"../integrations/internal-token.js\";\n\n/**\n * Framework route the background function actually runs — sibling to\n * `AGENT_TEAM_PROCESS_RUN_PATH`. Reached *through* the Netlify background\n * function, so it inherits the 15-min budget.\n */\nexport const AGENT_CHAT_PROCESS_RUN_PATH =\n \"/_agent-native/agent-chat/_process-run\";\n\n/**\n * Env flag for durable background runs. DEFAULT-ON: unset means enabled; an app\n * opts OUT with an explicit falsy value (`false`/`0`/`no`/`off`).\n */\nexport const AGENT_CHAT_DURABLE_BACKGROUND_ENV =\n \"AGENT_CHAT_DURABLE_BACKGROUND\";\n\n/**\n * Body field the foreground handler injects when self-dispatching to the\n * background processor. Its presence is how the re-entered handler knows it is\n * the background worker (run inline with the background soft-timeout; do NOT\n * re-claim the slot or re-dispatch). Untrusted on its own — the route also\n * verifies the HMAC token before invoking the handler.\n */\nexport const AGENT_CHAT_BACKGROUND_RUN_FIELD = \"__backgroundRun\";\n\n/**\n * Mirror of run-manager's private `isHostedRuntime`. Kept in sync deliberately:\n * the durable-background gate must agree with the soft-timeout regime about\n * what \"hosted\" means.\n */\nexport function isHostedRuntimeForDurableBackground(): boolean {\n if (\n process.env.NETLIFY &&\n process.env.NETLIFY !== \"false\" &&\n process.env.NETLIFY_LOCAL !== \"true\"\n ) {\n return true;\n }\n if (\n process.env.AWS_LAMBDA_FUNCTION_NAME &&\n process.env.NETLIFY_LOCAL !== \"true\"\n ) {\n return true;\n }\n return Boolean(\n process.env.CF_PAGES ||\n process.env.VERCEL ||\n process.env.VERCEL_ENV ||\n process.env.RENDER ||\n process.env.FLY_APP_NAME ||\n process.env.K_SERVICE,\n );\n}\n\n/**\n * True when THIS process is actually executing inside a Netlify *background*\n * function (the long, 15-min-budget async function whose deployed name ends in\n * `-background`). Netlify runs functions on AWS Lambda and sets\n * `AWS_LAMBDA_FUNCTION_NAME` to the function's name, so a `-background` suffix is\n * the runtime proof that the ~60s synchronous wall does NOT apply here.\n *\n * This is the SAFETY GUARD for the soft-timeout regime. The `_process-run`\n * self-dispatch worker (`isBackgroundWorker`) is NOT enough on its own: if the\n * `-background` function was never emitted (deploy gate off, or Netlify routed\n * the path to the synchronous function), the self-POST lands on the regular\n * ~60s `server` function. A worker there MUST use the 40s soft-timeout and\n * checkpoint before the 60s wall — using the ~13min budget would overshoot the\n * hard wall and get killed at 60s, then re-dispatch/resume in a wasteful loop.\n * So the 13-min budget is taken ONLY when this returns true.\n *\n * Falls back to the explicit `AGENT_CHAT_FORCE_BACKGROUND_RUNTIME` env (truthy)\n * for hosts that don't expose a `-background`-suffixed function name but where\n * the operator has confirmed a long async budget. Off by default.\n */\nexport function isInBackgroundFunctionRuntime(): boolean {\n const lambdaName = process.env.AWS_LAMBDA_FUNCTION_NAME;\n if (\n typeof lambdaName === \"string\" &&\n lambdaName.toLowerCase().endsWith(\"-background\")\n ) {\n return true;\n }\n const forced = process.env.AGENT_CHAT_FORCE_BACKGROUND_RUNTIME;\n if (forced != null) {\n const v = forced.trim().toLowerCase();\n return v === \"1\" || v === \"true\" || v === \"yes\" || v === \"on\";\n }\n return false;\n}\n\nfunction isFlagEnabled(): boolean {\n // Read the literal key (not `process.env[CONST]`) so guard:no-env-credentials\n // can statically verify it against the allowlisted `AGENT_*` prefix. Keep this\n // in sync with AGENT_CHAT_DURABLE_BACKGROUND_ENV.\n //\n // DEFAULT-ON: durable background runs are the desired behavior for every\n // hosted app. So an unset/empty/unknown flag means ON; an app opts OUT only\n // with an explicit falsy value. This still composes with the hosted +\n // A2A_SECRET gates below, so non-hosted / unconfigured apps stay synchronous.\n // Safety net: a failed dispatch degrades to a synchronous inline run (see\n // production-agent.ts), so default-on cannot break chat even if the\n // self-dispatch can't be delivered on a given app.\n const raw = process.env.AGENT_CHAT_DURABLE_BACKGROUND;\n if (raw == null) return true;\n const normalized = raw.trim().toLowerCase();\n return !(\n normalized === \"0\" ||\n normalized === \"false\" ||\n normalized === \"no\" ||\n normalized === \"off\"\n );\n}\n\n/**\n * The single gate. True when the flag is not explicitly disabled (default-on)\n * AND the runtime is hosted AND A2A_SECRET is configured. False otherwise — and\n * false means the current synchronous behavior is used, unchanged. So a local /\n * non-hosted / unconfigured app stays synchronous even with the flag defaulting\n * on; durable only engages where the runtime actually supports it.\n */\nexport function isAgentChatDurableBackgroundEnabled(): boolean {\n return (\n isFlagEnabled() &&\n isHostedRuntimeForDurableBackground() &&\n hasConfiguredA2ASecret()\n );\n}\n\n/** Decision returned by `prepareProcessRunRequest`. */\nexport type ProcessRunPreparation =\n | {\n ok: true;\n /** The pre-claimed run id the background worker must reuse. */\n runId: string;\n /** Body to stash for the re-entered handler (marker guaranteed present). */\n body: Record<string, unknown>;\n }\n | {\n ok: false;\n /** HTTP status the route should return. */\n status: number;\n /** Error payload. */\n error: string;\n };\n\n/**\n * Pure, transport-agnostic core of the `_process-run` route: validate the body,\n * authenticate the HMAC self-dispatch, and produce the body the re-entered\n * agent-chat handler should run as the background worker.\n *\n * Auth policy mirrors the agent-teams processor exactly:\n * - `A2A_SECRET` set → require a valid `verifyInternalToken(runId, token)`.\n * - no secret but a production runtime → refuse (503) — never run unsigned in\n * prod.\n * - no secret + non-prod (local dev) → allow unsigned; the SQL atomic claim\n * in the worker still prevents double-processing.\n *\n * Extracted from the route handler so the auth + marker-prep decision is unit\n * testable without booting the whole Nitro plugin. The route only adds body\n * reading and the final handler invocation around this.\n */\nexport function prepareProcessRunRequest(\n body: unknown,\n authHeader: string | undefined,\n): ProcessRunPreparation {\n if (!body || typeof body !== \"object\") {\n return { ok: false, status: 400, error: \"Invalid request body\" };\n }\n const record = body as Record<string, unknown>;\n const marker = record[AGENT_CHAT_BACKGROUND_RUN_FIELD] as\n | { runId?: unknown }\n | undefined;\n const runId =\n marker && typeof marker.runId === \"string\"\n ? marker.runId\n : typeof record.taskId === \"string\"\n ? (record.taskId as string)\n : \"\";\n if (!runId) {\n return { ok: false, status: 400, error: \"runId required\" };\n }\n\n if (hasConfiguredA2ASecret()) {\n const token = extractBearerToken(authHeader);\n if (!verifyInternalToken(runId, token ?? \"\")) {\n return {\n ok: false,\n status: 401,\n error: \"Invalid or expired processor token\",\n };\n }\n } else if (isA2AProductionRuntime()) {\n return {\n ok: false,\n status: 503,\n error:\n \"Agent chat background processor not configured — set A2A_SECRET on this deployment.\",\n };\n }\n\n // Ensure the marker is present so the re-entered handler runs as the\n // background worker (reuses runId/turnId, no re-claim, no re-dispatch).\n if (!marker || typeof marker.runId !== \"string\") {\n record[AGENT_CHAT_BACKGROUND_RUN_FIELD] = { runId };\n }\n return { ok: true, runId, body: record };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"production-agent.d.ts","sourceRoot":"","sources":["../../src/agent/production-agent.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,IAAI,cAAc,EAAE,MAAM,IAAI,CAAC;AACzD,OAAO,KAAK,EACV,UAAU,EAEV,mBAAmB,EAEnB,cAAc,EACd,kBAAkB,EAClB,0BAA0B,EAC3B,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,aAAa,EACb,iBAAiB,EAGlB,MAAM,mBAAmB,CAAC;AAmB3B,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAShE,OAAO,EAEL,cAAc,EACd,qBAAqB,EACrB,0BAA0B,EAC1B,MAAM,EACN,QAAQ,EAET,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"production-agent.d.ts","sourceRoot":"","sources":["../../src/agent/production-agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,7 +12,7 @@ import { isDemoModeEnabled } from "../demo/config.js";
|
|
|
12
12
|
import { redactDemoData, redactDemoString } from "../demo/redact.js";
|
|
13
13
|
import { redactSensitiveFields, sanitizeToolErrorText, sanitizeToolErrorValue, } from "./tool-error-redaction.js";
|
|
14
14
|
import { startRun, subscribeToRun, getActiveRunForThread, getActiveRunForThreadAsync, getRun, abortRun, tryClaimRunSlot, } from "./run-manager.js";
|
|
15
|
-
import { AGENT_CHAT_PROCESS_RUN_PATH, AGENT_CHAT_BACKGROUND_RUN_FIELD, isAgentChatDurableBackgroundEnabled, } from "./durable-background.js";
|
|
15
|
+
import { AGENT_CHAT_PROCESS_RUN_PATH, AGENT_CHAT_BACKGROUND_RUN_FIELD, isAgentChatDurableBackgroundEnabled, isInBackgroundFunctionRuntime, } from "./durable-background.js";
|
|
16
16
|
import { fireInternalDispatch } from "../server/self-dispatch.js";
|
|
17
17
|
import { readBody } from "../server/h3-helpers.js";
|
|
18
18
|
import { isReadOnlyShellCommand } from "../coding-tools/index.js";
|
|
@@ -2810,6 +2810,12 @@ export function createProductionAgentHandler(options) {
|
|
|
2810
2810
|
? body[AGENT_CHAT_BACKGROUND_RUN_FIELD]
|
|
2811
2811
|
: null;
|
|
2812
2812
|
const isBackgroundWorker = backgroundRunMarker !== null;
|
|
2813
|
+
// Whether this worker is REALLY executing inside a 15-min Netlify
|
|
2814
|
+
// `-background` function (proven by the runtime function name), not merely a
|
|
2815
|
+
// `_process-run` re-entry that may have landed on the ~60s synchronous
|
|
2816
|
+
// function. Only a true value unlocks the ~13-min soft-timeout budget; a
|
|
2817
|
+
// worker on the 60s function keeps the 40s clamp and checkpoints cleanly.
|
|
2818
|
+
const runsInBackgroundFunction = isBackgroundWorker && isInBackgroundFunctionRuntime();
|
|
2813
2819
|
// How many server-driven background continuations have already chained into
|
|
2814
2820
|
// this logical turn (0 on the first chunk). Used to bound the chain.
|
|
2815
2821
|
const backgroundContinuationCount = isBackgroundWorker &&
|
|
@@ -3531,6 +3537,23 @@ export function createProductionAgentHandler(options) {
|
|
|
3531
3537
|
// chain. Bounded by MAX_BACKGROUND_RUN_CONTINUATIONS. Aborted /
|
|
3532
3538
|
// user-stopped runs do NOT chain.
|
|
3533
3539
|
if (shouldChainBackgroundContinuation({
|
|
3540
|
+
// Self-chain server-side for EVERY durable worker, not only the
|
|
3541
|
+
// ones inside a `-background` function. Server-driven
|
|
3542
|
+
// continuation is the whole point of durable background: the run
|
|
3543
|
+
// must survive the client disconnecting (closed tab), so it
|
|
3544
|
+
// cannot depend on the browser re-POSTing `auto_continue`. A
|
|
3545
|
+
// worker on the regular ~60s function — a Netlify routing miss,
|
|
3546
|
+
// or a non-Netlify host (Vercel/Cloudflare/Render/Fly) that
|
|
3547
|
+
// never emits a `-background` function — checkpoints at the 40s
|
|
3548
|
+
// soft-timeout and self-dispatches the next 40s chunk; a worker
|
|
3549
|
+
// in a real `-background` function chains ~13-min chunks. Only
|
|
3550
|
+
// the per-chunk BUDGET differs by function type (gated by
|
|
3551
|
+
// `runsInBackgroundFunction` at the startRun call below); the
|
|
3552
|
+
// continuation itself must stay server-driven on both. (The
|
|
3553
|
+
// self-chain is only reachable when the initial dispatch already
|
|
3554
|
+
// succeeded — a dispatch fast-fail degrades to the inline
|
|
3555
|
+
// foreground fallback, which is not a worker and rides the
|
|
3556
|
+
// connected client's auto_continue instead.)
|
|
3534
3557
|
isBackgroundWorker,
|
|
3535
3558
|
run,
|
|
3536
3559
|
continuationCount: backgroundContinuationCount,
|
|
@@ -3928,10 +3951,18 @@ export function createProductionAgentHandler(options) {
|
|
|
3928
3951
|
}, handleRunComplete, {
|
|
3929
3952
|
softTimeoutMs: options.runSoftTimeoutMs,
|
|
3930
3953
|
useHostedSoftTimeoutDefault: true,
|
|
3931
|
-
//
|
|
3932
|
-
//
|
|
3933
|
-
//
|
|
3934
|
-
|
|
3954
|
+
// Lift the soft-timeout clamp to ~13min ONLY when this run is actually
|
|
3955
|
+
// executing inside a real Netlify `-background` function (15-min budget,
|
|
3956
|
+
// no ~60s wall). Being the `_process-run` worker (`isBackgroundWorker`)
|
|
3957
|
+
// is NOT sufficient: if the `-background` function wasn't emitted, or
|
|
3958
|
+
// Netlify routed the self-POST to the synchronous function, the worker
|
|
3959
|
+
// landed on the regular ~60s `server` function — there it MUST keep the
|
|
3960
|
+
// 40s clamp and checkpoint before the wall, or it overshoots the 60s
|
|
3961
|
+
// hard kill and re-dispatches in a loop. `runsInBackgroundFunction`
|
|
3962
|
+
// gates the 13-min budget on the proven runtime, not merely on "I'm the
|
|
3963
|
+
// worker." Foreground runs never set this, so their 40s clamp is
|
|
3964
|
+
// unchanged.
|
|
3965
|
+
backgroundFunction: runsInBackgroundFunction,
|
|
3935
3966
|
// Fold continuation runs of one logical turn onto a single durable
|
|
3936
3967
|
// assistant message. Falls back to the runId (turn == run) when the
|
|
3937
3968
|
// client doesn't supply a turnId.
|