@agent-native/core 0.84.39 → 0.84.41
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 +12 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/action-continuation-guidance.ts +38 -0
- package/corpus/core/src/agent/production-agent.ts +344 -34
- package/corpus/core/src/agent/run-loop-with-resume.ts +128 -16
- package/corpus/core/src/agent/types.ts +8 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +2 -40
- package/dist/agent/action-continuation-guidance.d.ts +3 -0
- package/dist/agent/action-continuation-guidance.d.ts.map +1 -0
- package/dist/agent/action-continuation-guidance.js +38 -0
- package/dist/agent/action-continuation-guidance.js.map +1 -0
- package/dist/agent/production-agent.d.ts +8 -5
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +267 -32
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +69 -18
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/agent/types.d.ts +2 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +2 -39
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/package.json +1 -1
|
@@ -16,6 +16,7 @@ import { readBody } from "../server/h3-helpers.js";
|
|
|
16
16
|
import { getRequestRunContext, ensureRequestRunContext, getRequestContext, getRequestOrgId, getRequestUserEmail, runWithRequestContext, } from "../server/request-context.js";
|
|
17
17
|
import { fireInternalDispatch } from "../server/self-dispatch.js";
|
|
18
18
|
import { isReasoningEffort, normalizeReasoningEffortForModel, } from "../shared/reasoning-effort.js";
|
|
19
|
+
import { actionPreparationContinuationNote } from "./action-continuation-guidance.js";
|
|
19
20
|
import { applyContextDirectives } from "./context-xray/apply-directives.js";
|
|
20
21
|
import { loadContextDirectives } from "./context-xray/directives-store.js";
|
|
21
22
|
import { buildManifest, writeContextManifest, } from "./context-xray/manifest.js";
|
|
@@ -558,6 +559,7 @@ function maxRetriesForError(err) {
|
|
|
558
559
|
return MAX_RETRIES;
|
|
559
560
|
}
|
|
560
561
|
const TOOL_INPUT_ACTIVITY_INTERVAL_MS = 1500;
|
|
562
|
+
const ACTION_PREPARATION_NO_PROGRESS_TIMEOUT_MS = 90_000;
|
|
561
563
|
const MAX_TEXT_ATTACHMENT_CHARS = 60_000;
|
|
562
564
|
const MAX_SELECTION_CONTEXT_CHARS = 8_000;
|
|
563
565
|
const MAX_RESOURCE_INVENTORY_ITEMS = 40;
|
|
@@ -1098,7 +1100,7 @@ function collectTextParts(parts) {
|
|
|
1098
1100
|
.join("");
|
|
1099
1101
|
}
|
|
1100
1102
|
export const AGENT_INTERNAL_CONTINUE_PROMPT = "Continue from where you left off and finish the user's original request. Do not repeat completed work, do not mention internal reconnects, time limits, or step limits, and continue as if this is the same uninterrupted run.";
|
|
1101
|
-
export function appendAgentLoopContinuation(messages, reason) {
|
|
1103
|
+
export function appendAgentLoopContinuation(messages, reason, options = {}) {
|
|
1102
1104
|
const note = reason === "loop_limit"
|
|
1103
1105
|
? "The previous run reached an internal step budget."
|
|
1104
1106
|
: reason === "max_tokens"
|
|
@@ -1109,17 +1111,31 @@ export function appendAgentLoopContinuation(messages, reason) {
|
|
|
1109
1111
|
? "The previous LLM call hit an upstream gateway timeout before the response finished streaming."
|
|
1110
1112
|
: reason === "network_interrupted"
|
|
1111
1113
|
? "The previous LLM call was cut off by a transport-level interruption (socket dropped, connection reset, or stream closed unexpectedly)."
|
|
1112
|
-
:
|
|
1114
|
+
: reason === "no_progress"
|
|
1115
|
+
? "The previous run stopped producing progress events while the connection stayed open."
|
|
1116
|
+
: "The previous run reached an internal execution budget.";
|
|
1117
|
+
const actionInputNote = options.actionPreparationTool
|
|
1118
|
+
? actionPreparationContinuationNote(options.actionPreparationTool)
|
|
1119
|
+
: "";
|
|
1113
1120
|
messages.push({
|
|
1114
1121
|
role: "user",
|
|
1115
1122
|
content: [
|
|
1116
1123
|
{
|
|
1117
1124
|
type: "text",
|
|
1118
|
-
text: `${AGENT_INTERNAL_CONTINUE_PROMPT}\n\nInternal note: ${note}`,
|
|
1125
|
+
text: `${AGENT_INTERNAL_CONTINUE_PROMPT}\n\nInternal note: ${note}${actionInputNote}`,
|
|
1119
1126
|
},
|
|
1120
1127
|
],
|
|
1121
1128
|
});
|
|
1122
1129
|
}
|
|
1130
|
+
function isAgentLoopContinuationReason(reason) {
|
|
1131
|
+
return (reason === "run_timeout" ||
|
|
1132
|
+
reason === "loop_limit" ||
|
|
1133
|
+
reason === "max_tokens" ||
|
|
1134
|
+
reason === "stream_ended" ||
|
|
1135
|
+
reason === "gateway_timeout" ||
|
|
1136
|
+
reason === "network_interrupted" ||
|
|
1137
|
+
reason === "no_progress");
|
|
1138
|
+
}
|
|
1123
1139
|
/**
|
|
1124
1140
|
* True when an error thrown by `runAgentLoop` is a recoverable transport- or
|
|
1125
1141
|
* gateway-level interruption that the agent can resume from rather than a
|
|
@@ -1960,6 +1976,8 @@ export async function runAgentLoop(opts) {
|
|
|
1960
1976
|
const toolInputNames = new Map();
|
|
1961
1977
|
const toolInputBytes = new Map();
|
|
1962
1978
|
let lastToolInputActivityAt = 0;
|
|
1979
|
+
const activeToolInputs = new Map();
|
|
1980
|
+
let endedForActionPreparationNoProgress = false;
|
|
1963
1981
|
const sendToolInputActivity = (toolName, toolInputId, progressBytes, force = false) => {
|
|
1964
1982
|
const now = Date.now();
|
|
1965
1983
|
if (!force &&
|
|
@@ -1975,7 +1993,56 @@ export async function runAgentLoop(opts) {
|
|
|
1975
1993
|
...(typeof progressBytes === "number" ? { progressBytes } : {}),
|
|
1976
1994
|
});
|
|
1977
1995
|
};
|
|
1996
|
+
const resetActiveToolInput = (toolName, toolInputId) => {
|
|
1997
|
+
if (toolInputId) {
|
|
1998
|
+
activeToolInputs.delete(toolInputId);
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
if (toolName) {
|
|
2002
|
+
for (const [id, active] of activeToolInputs) {
|
|
2003
|
+
if (active.toolName === toolName) {
|
|
2004
|
+
activeToolInputs.delete(id);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
const hasActionPreparationStalled = () => {
|
|
2010
|
+
if (activeToolInputs.size === 0)
|
|
2011
|
+
return false;
|
|
2012
|
+
const now = Date.now();
|
|
2013
|
+
for (const active of activeToolInputs.values()) {
|
|
2014
|
+
if (now - active.lastProgressAt >=
|
|
2015
|
+
ACTION_PREPARATION_NO_PROGRESS_TIMEOUT_MS) {
|
|
2016
|
+
return true;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
return false;
|
|
2020
|
+
};
|
|
2021
|
+
const trackActiveToolInput = (key, toolName, bytes) => {
|
|
2022
|
+
const now = Date.now();
|
|
2023
|
+
const previous = activeToolInputs.get(key);
|
|
2024
|
+
activeToolInputs.set(key, {
|
|
2025
|
+
id: key,
|
|
2026
|
+
...((toolName ?? previous?.toolName)
|
|
2027
|
+
? { toolName: toolName ?? previous?.toolName }
|
|
2028
|
+
: {}),
|
|
2029
|
+
startedAt: previous?.startedAt ?? now,
|
|
2030
|
+
lastProgressAt: now,
|
|
2031
|
+
bytes,
|
|
2032
|
+
});
|
|
2033
|
+
};
|
|
2034
|
+
const clearActiveToolInputs = () => {
|
|
2035
|
+
activeToolInputs.clear();
|
|
2036
|
+
};
|
|
1978
2037
|
for await (const event of eventStream) {
|
|
2038
|
+
if (hasActionPreparationStalled()) {
|
|
2039
|
+
send({
|
|
2040
|
+
type: "auto_continue",
|
|
2041
|
+
reason: "no_progress",
|
|
2042
|
+
});
|
|
2043
|
+
endedForActionPreparationNoProgress = true;
|
|
2044
|
+
break;
|
|
2045
|
+
}
|
|
1979
2046
|
// In-loop processor seam (stream hook). Each chunk is offered to every
|
|
1980
2047
|
// processor's `processOutputStream` before the loop handles it. A
|
|
1981
2048
|
// processor `abort()` throws a TripWire; catch it locally so it is not
|
|
@@ -2008,6 +2075,7 @@ export async function runAgentLoop(opts) {
|
|
|
2008
2075
|
if (key && event.name) {
|
|
2009
2076
|
toolInputNames.set(key, event.name);
|
|
2010
2077
|
toolInputBytes.set(key, 0);
|
|
2078
|
+
trackActiveToolInput(key, event.name, 0);
|
|
2011
2079
|
}
|
|
2012
2080
|
sendToolInputActivity(event.name, key, undefined, true);
|
|
2013
2081
|
}
|
|
@@ -2022,6 +2090,9 @@ export async function runAgentLoop(opts) {
|
|
|
2022
2090
|
previous +
|
|
2023
2091
|
new TextEncoder().encode(event.text ?? "").byteLength;
|
|
2024
2092
|
toolInputBytes.set(key, progressBytes);
|
|
2093
|
+
if (progressBytes > previous) {
|
|
2094
|
+
trackActiveToolInput(key, toolName, progressBytes);
|
|
2095
|
+
}
|
|
2025
2096
|
}
|
|
2026
2097
|
sendToolInputActivity(toolName, key, progressBytes);
|
|
2027
2098
|
}
|
|
@@ -2030,8 +2101,10 @@ export async function runAgentLoop(opts) {
|
|
|
2030
2101
|
}
|
|
2031
2102
|
else if (event.type === "tool-call") {
|
|
2032
2103
|
// The authoritative tool-call blocks arrive in assistant-content.
|
|
2104
|
+
resetActiveToolInput(event.name, event.id);
|
|
2033
2105
|
}
|
|
2034
2106
|
else if (event.type === "tool-call-error") {
|
|
2107
|
+
resetActiveToolInput(event.name, event.id);
|
|
2035
2108
|
toolCallErrors.set(event.id, {
|
|
2036
2109
|
name: event.name,
|
|
2037
2110
|
input: event.input,
|
|
@@ -2039,6 +2112,7 @@ export async function runAgentLoop(opts) {
|
|
|
2039
2112
|
});
|
|
2040
2113
|
}
|
|
2041
2114
|
else if (event.type === "assistant-content") {
|
|
2115
|
+
clearActiveToolInputs();
|
|
2042
2116
|
assistantContent = event.parts;
|
|
2043
2117
|
}
|
|
2044
2118
|
else if (event.type === "usage") {
|
|
@@ -2058,6 +2132,17 @@ export async function runAgentLoop(opts) {
|
|
|
2058
2132
|
});
|
|
2059
2133
|
}
|
|
2060
2134
|
}
|
|
2135
|
+
if (hasActionPreparationStalled()) {
|
|
2136
|
+
send({
|
|
2137
|
+
type: "auto_continue",
|
|
2138
|
+
reason: "no_progress",
|
|
2139
|
+
});
|
|
2140
|
+
endedForActionPreparationNoProgress = true;
|
|
2141
|
+
break;
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
if (endedForActionPreparationNoProgress) {
|
|
2145
|
+
return usage;
|
|
2061
2146
|
}
|
|
2062
2147
|
break;
|
|
2063
2148
|
}
|
|
@@ -3043,6 +3128,141 @@ function endsAtInternalContinuationBoundary(run) {
|
|
|
3043
3128
|
}
|
|
3044
3129
|
return last.type === "error" && isRecoverableContinuationError(last);
|
|
3045
3130
|
}
|
|
3131
|
+
function isPreparingActionActivityEvent(event) {
|
|
3132
|
+
if (event.type !== "activity")
|
|
3133
|
+
return false;
|
|
3134
|
+
const label = event.label.trim().toLowerCase();
|
|
3135
|
+
return label.startsWith("preparing ") && label.includes(" action");
|
|
3136
|
+
}
|
|
3137
|
+
export function lastUnfinishedPreparingActionToolFromEvents(events) {
|
|
3138
|
+
const active = new Map();
|
|
3139
|
+
const idlessToolStarts = new Map();
|
|
3140
|
+
const removeOldestMatchingActivePreparation = (tool, shouldRemove = () => true) => {
|
|
3141
|
+
let oldest;
|
|
3142
|
+
for (const [key, value] of active) {
|
|
3143
|
+
if (value.tool !== tool || !shouldRemove(value))
|
|
3144
|
+
continue;
|
|
3145
|
+
if (!oldest || value.order < oldest.order) {
|
|
3146
|
+
oldest = {
|
|
3147
|
+
key,
|
|
3148
|
+
order: value.order,
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
if (oldest)
|
|
3153
|
+
active.delete(oldest.key);
|
|
3154
|
+
return Boolean(oldest);
|
|
3155
|
+
};
|
|
3156
|
+
const removeMatchingActivePreparation = (event) => {
|
|
3157
|
+
const id = event.id?.trim();
|
|
3158
|
+
const tool = event.tool?.trim();
|
|
3159
|
+
if (!tool)
|
|
3160
|
+
return;
|
|
3161
|
+
if (id) {
|
|
3162
|
+
if (!active.delete(`id:${id}`)) {
|
|
3163
|
+
removeOldestMatchingActivePreparation(tool, (value) => !value.id);
|
|
3164
|
+
}
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
if (event.type === "tool_start") {
|
|
3168
|
+
if (removeOldestMatchingActivePreparation(tool)) {
|
|
3169
|
+
idlessToolStarts.set(tool, (idlessToolStarts.get(tool) ?? 0) + 1);
|
|
3170
|
+
}
|
|
3171
|
+
return;
|
|
3172
|
+
}
|
|
3173
|
+
const startedCount = idlessToolStarts.get(tool) ?? 0;
|
|
3174
|
+
if (startedCount > 0) {
|
|
3175
|
+
if (startedCount === 1) {
|
|
3176
|
+
idlessToolStarts.delete(tool);
|
|
3177
|
+
}
|
|
3178
|
+
else {
|
|
3179
|
+
idlessToolStarts.set(tool, startedCount - 1);
|
|
3180
|
+
}
|
|
3181
|
+
return;
|
|
3182
|
+
}
|
|
3183
|
+
removeOldestMatchingActivePreparation(tool);
|
|
3184
|
+
};
|
|
3185
|
+
events.forEach((event, order) => {
|
|
3186
|
+
if (isPreparingActionActivityEvent(event)) {
|
|
3187
|
+
const tool = event.tool?.trim();
|
|
3188
|
+
if (tool) {
|
|
3189
|
+
const id = event.id?.trim();
|
|
3190
|
+
const key = id ? `id:${id}` : `tool:${tool}:${order}`;
|
|
3191
|
+
active.set(key, {
|
|
3192
|
+
tool,
|
|
3193
|
+
order,
|
|
3194
|
+
...(id ? { id } : {}),
|
|
3195
|
+
});
|
|
3196
|
+
}
|
|
3197
|
+
return;
|
|
3198
|
+
}
|
|
3199
|
+
if (event.type === "tool_start" || event.type === "tool_done") {
|
|
3200
|
+
removeMatchingActivePreparation(event);
|
|
3201
|
+
return;
|
|
3202
|
+
}
|
|
3203
|
+
if (event.type === "error" && isRecoverableContinuationError(event)) {
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3206
|
+
if (event.type === "clear" ||
|
|
3207
|
+
event.type === "done" ||
|
|
3208
|
+
event.type === "error" ||
|
|
3209
|
+
event.type === "missing_api_key") {
|
|
3210
|
+
active.clear();
|
|
3211
|
+
idlessToolStarts.clear();
|
|
3212
|
+
}
|
|
3213
|
+
});
|
|
3214
|
+
let latest;
|
|
3215
|
+
for (const value of active.values()) {
|
|
3216
|
+
if (!latest || value.order > latest.order) {
|
|
3217
|
+
latest = value;
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
return latest?.tool;
|
|
3221
|
+
}
|
|
3222
|
+
function endsAfterCompletedToolWithoutAssistantFinal(run) {
|
|
3223
|
+
let completedToolAfterLastAssistantText = false;
|
|
3224
|
+
for (const { event } of run.events) {
|
|
3225
|
+
if (event.type === "text" && event.text.trim().length > 0) {
|
|
3226
|
+
completedToolAfterLastAssistantText = false;
|
|
3227
|
+
continue;
|
|
3228
|
+
}
|
|
3229
|
+
if (event.type === "tool_done" && event.isError !== true) {
|
|
3230
|
+
completedToolAfterLastAssistantText = true;
|
|
3231
|
+
continue;
|
|
3232
|
+
}
|
|
3233
|
+
if (event.type === "clear" ||
|
|
3234
|
+
event.type === "error" ||
|
|
3235
|
+
event.type === "missing_api_key" ||
|
|
3236
|
+
event.type === "auto_continue" ||
|
|
3237
|
+
event.type === "loop_limit") {
|
|
3238
|
+
completedToolAfterLastAssistantText = false;
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
return completedToolAfterLastAssistantText;
|
|
3242
|
+
}
|
|
3243
|
+
function lastUnfinishedPreparingActionTool(run) {
|
|
3244
|
+
return lastUnfinishedPreparingActionToolFromEvents(run.events.map(({ event }) => event));
|
|
3245
|
+
}
|
|
3246
|
+
export function backgroundContinuationReasonForRun(run) {
|
|
3247
|
+
const last = run.events.at(-1)?.event;
|
|
3248
|
+
if (last?.type === "loop_limit")
|
|
3249
|
+
return "loop_limit";
|
|
3250
|
+
if (last?.type === "auto_continue" &&
|
|
3251
|
+
isAgentLoopContinuationReason(last.reason)) {
|
|
3252
|
+
return last.reason;
|
|
3253
|
+
}
|
|
3254
|
+
if (last?.type === "error" && isRecoverableContinuationError(last)) {
|
|
3255
|
+
return continuationReasonForResumableError(new EngineError(last.error, { errorCode: last.errorCode }));
|
|
3256
|
+
}
|
|
3257
|
+
if (endsAfterCompletedToolWithoutAssistantFinal(run)) {
|
|
3258
|
+
return "stream_ended";
|
|
3259
|
+
}
|
|
3260
|
+
return "run_timeout";
|
|
3261
|
+
}
|
|
3262
|
+
function endsAtContinuationBoundary(run) {
|
|
3263
|
+
return (endsAtInternalContinuationBoundary(run) ||
|
|
3264
|
+
endsAfterCompletedToolWithoutAssistantFinal(run));
|
|
3265
|
+
}
|
|
3046
3266
|
/**
|
|
3047
3267
|
* Hard cap on server-driven background→background continuation chunks for a
|
|
3048
3268
|
* single logical turn. A `backgroundFunction` run gets a ~13-min soft timeout,
|
|
@@ -3054,14 +3274,13 @@ export const MAX_BACKGROUND_RUN_CONTINUATIONS = 20;
|
|
|
3054
3274
|
/**
|
|
3055
3275
|
* Whether the background worker should self-fire the next server-driven
|
|
3056
3276
|
* continuation chunk. True only when this is a background worker run that ended
|
|
3057
|
-
* at a recoverable
|
|
3058
|
-
* still under its budget.
|
|
3059
|
-
* booting the whole handler.
|
|
3277
|
+
* at a recoverable unfinished boundary (not aborted/stopped) and the chain is
|
|
3278
|
+
* still under its budget. Aborted / user-stopped runs do NOT chain.
|
|
3060
3279
|
*/
|
|
3061
3280
|
export function shouldChainBackgroundContinuation(opts) {
|
|
3062
3281
|
return (opts.isBackgroundWorker &&
|
|
3063
3282
|
opts.run.status !== "aborted" &&
|
|
3064
|
-
|
|
3283
|
+
endsAtContinuationBoundary(opts.run) &&
|
|
3065
3284
|
opts.continuationCount < MAX_BACKGROUND_RUN_CONTINUATIONS);
|
|
3066
3285
|
}
|
|
3067
3286
|
export async function claimBackgroundWorkerRunEarly(opts) {
|
|
@@ -3809,7 +4028,16 @@ export function createProductionAgentHandler(options) {
|
|
|
3809
4028
|
?.threadData;
|
|
3810
4029
|
const resumed = threadDataToEngineMessages(priorThreadData);
|
|
3811
4030
|
if (resumed.length > 0) {
|
|
3812
|
-
|
|
4031
|
+
const actionPreparationTool = typeof backgroundRunMarker?.actionPreparationTool === "string" &&
|
|
4032
|
+
backgroundRunMarker.actionPreparationTool.trim()
|
|
4033
|
+
? backgroundRunMarker.actionPreparationTool.trim()
|
|
4034
|
+
: undefined;
|
|
4035
|
+
const continuationReason = isAgentLoopContinuationReason(backgroundRunMarker?.continuationReason)
|
|
4036
|
+
? backgroundRunMarker.continuationReason
|
|
4037
|
+
: "run_timeout";
|
|
4038
|
+
appendAgentLoopContinuation(resumed, continuationReason, {
|
|
4039
|
+
...(actionPreparationTool ? { actionPreparationTool } : {}),
|
|
4040
|
+
});
|
|
3813
4041
|
messages.length = 0;
|
|
3814
4042
|
messages.push(...resumed);
|
|
3815
4043
|
}
|
|
@@ -3972,10 +4200,15 @@ export function createProductionAgentHandler(options) {
|
|
|
3972
4200
|
turnId: effectiveTurnId,
|
|
3973
4201
|
}
|
|
3974
4202
|
: null;
|
|
4203
|
+
const willChainBackgroundContinuation = (run) => shouldChainBackgroundContinuation({
|
|
4204
|
+
isBackgroundWorker,
|
|
4205
|
+
run,
|
|
4206
|
+
continuationCount: backgroundContinuationCount,
|
|
4207
|
+
});
|
|
3975
4208
|
const completeTrackedProgressRun = async (run, completionError) => {
|
|
3976
4209
|
if (!trackedProgressRunId || !trackedProgressOwner)
|
|
3977
4210
|
return;
|
|
3978
|
-
if (!completionError &&
|
|
4211
|
+
if (!completionError && willChainBackgroundContinuation(run)) {
|
|
3979
4212
|
return;
|
|
3980
4213
|
}
|
|
3981
4214
|
const terminalStatus = run.status === "aborted"
|
|
@@ -4060,7 +4293,7 @@ export function createProductionAgentHandler(options) {
|
|
|
4060
4293
|
// below, they did not "throw").
|
|
4061
4294
|
if (isBackgroundWorker &&
|
|
4062
4295
|
run.status === "errored" &&
|
|
4063
|
-
!
|
|
4296
|
+
!willChainBackgroundContinuation(run)) {
|
|
4064
4297
|
const errEvent = [...run.events]
|
|
4065
4298
|
.reverse()
|
|
4066
4299
|
.find((e) => e.event.type === "error")?.event;
|
|
@@ -4081,34 +4314,32 @@ export function createProductionAgentHandler(options) {
|
|
|
4081
4314
|
// agent-teams `fireInternalDispatch({ body: { mode: "continue" }})`
|
|
4082
4315
|
// chain. Bounded by MAX_BACKGROUND_RUN_CONTINUATIONS. Aborted /
|
|
4083
4316
|
// user-stopped runs do NOT chain.
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
isBackgroundWorker,
|
|
4103
|
-
run,
|
|
4104
|
-
continuationCount: backgroundContinuationCount,
|
|
4105
|
-
})) {
|
|
4317
|
+
// Self-chain server-side for EVERY durable worker, not only the
|
|
4318
|
+
// ones inside a `-background` function. Server-driven
|
|
4319
|
+
// continuation is the whole point of durable background: the run
|
|
4320
|
+
// must survive the client disconnecting (closed tab), so it
|
|
4321
|
+
// cannot depend on the browser re-POSTing `auto_continue`. A
|
|
4322
|
+
// worker on the regular ~60s function — a Netlify routing miss,
|
|
4323
|
+
// or a non-Netlify host (Vercel/Cloudflare/Render/Fly) that
|
|
4324
|
+
// never emits a `-background` function — checkpoints at the 40s
|
|
4325
|
+
// soft-timeout and self-dispatches the next 40s chunk; a worker
|
|
4326
|
+
// in a real `-background` function chains ~13-min chunks. Only
|
|
4327
|
+
// the per-chunk BUDGET differs by function type (gated by
|
|
4328
|
+
// `runsInBackgroundFunction` at the startRun call below); the
|
|
4329
|
+
// continuation itself must stay server-driven on both. (The
|
|
4330
|
+
// self-chain is only reachable when the initial dispatch already
|
|
4331
|
+
// succeeded — a dispatch fast-fail degrades to the inline
|
|
4332
|
+
// foreground fallback, which is not a worker and rides the
|
|
4333
|
+
// connected client's auto_continue instead.)
|
|
4334
|
+
if (willChainBackgroundContinuation(run)) {
|
|
4106
4335
|
// Mint the next chunk's runId here and sign the dispatch token
|
|
4107
4336
|
// over it, so the `_process-run` route's HMAC check and the
|
|
4108
4337
|
// worker's run identity agree. Fresh runId (not this chunk's) so
|
|
4109
4338
|
// its seq log starts clean; same turnId folds the assistant
|
|
4110
4339
|
// message across chunks.
|
|
4111
4340
|
const nextRunId = generateRunId();
|
|
4341
|
+
const actionPreparationTool = lastUnfinishedPreparingActionTool(run);
|
|
4342
|
+
const continuationReason = backgroundContinuationReasonForRun(run);
|
|
4112
4343
|
const continuationDispatchPath = resolveAgentChatProcessRunDispatchPath();
|
|
4113
4344
|
const continuationExpectsNetlifyBackgroundFunction = dispatchPathTargetsNetlifyBackgroundFunction(continuationDispatchPath);
|
|
4114
4345
|
try {
|
|
@@ -4129,6 +4360,10 @@ export function createProductionAgentHandler(options) {
|
|
|
4129
4360
|
runId: nextRunId,
|
|
4130
4361
|
turnId: effectiveTurnId,
|
|
4131
4362
|
continuationCount: backgroundContinuationCount + 1,
|
|
4363
|
+
continuationReason,
|
|
4364
|
+
...(actionPreparationTool
|
|
4365
|
+
? { actionPreparationTool }
|
|
4366
|
+
: {}),
|
|
4132
4367
|
backgroundFunctionRuntimeExpected: continuationExpectsNetlifyBackgroundFunction,
|
|
4133
4368
|
},
|
|
4134
4369
|
},
|