@agent-native/core 0.84.62 → 0.84.64
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/production-agent.ts +211 -120
- package/corpus/templates/design/changelog/2026-07-02-design-chat-now-recovers-when-the-model-connection-goes-quie.md +6 -0
- package/corpus/templates/design/changelog/2026-07-02-design-now-recovers-cleanly-when-an-edit-screen-action-stall.md +6 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +207 -128
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +7 -7
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -544,6 +544,7 @@ function maxRetriesForError(err) {
|
|
|
544
544
|
const TOOL_INPUT_ACTIVITY_INTERVAL_MS = 1500;
|
|
545
545
|
const ACTION_PREPARATION_NO_PROGRESS_TIMEOUT_MS = 90_000;
|
|
546
546
|
const ACTION_PREPARATION_ZERO_BYTE_RESTART_LIMIT = 2;
|
|
547
|
+
const MODEL_STREAM_NO_PROGRESS_TIMEOUT_MS = 90_000;
|
|
547
548
|
const MAX_TEXT_ATTACHMENT_CHARS = 60_000;
|
|
548
549
|
const MAX_SELECTION_CONTEXT_CHARS = 8_000;
|
|
549
550
|
const MAX_RESOURCE_INVENTORY_ITEMS = 40;
|
|
@@ -1962,7 +1963,8 @@ export async function runAgentLoop(opts) {
|
|
|
1962
1963
|
let lastToolInputActivityAt = 0;
|
|
1963
1964
|
const activeToolInputs = new Map();
|
|
1964
1965
|
let zeroByteToolInputRestart;
|
|
1965
|
-
let
|
|
1966
|
+
let endedForNoProgress = false;
|
|
1967
|
+
let lastModelStreamProgressAt = Date.now();
|
|
1966
1968
|
const sendToolInputActivity = (toolName, toolInputId, progressBytes, force = false) => {
|
|
1967
1969
|
const now = Date.now();
|
|
1968
1970
|
if (!force &&
|
|
@@ -1978,10 +1980,12 @@ export async function runAgentLoop(opts) {
|
|
|
1978
1980
|
...(typeof progressBytes === "number" ? { progressBytes } : {}),
|
|
1979
1981
|
});
|
|
1980
1982
|
};
|
|
1981
|
-
const
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1983
|
+
const actionPreparationDeadlineAt = () => {
|
|
1984
|
+
let deadlineAt = Number.POSITIVE_INFINITY;
|
|
1985
|
+
if (zeroByteToolInputRestart) {
|
|
1986
|
+
deadlineAt = Math.min(deadlineAt, zeroByteToolInputRestart.firstStartedAt +
|
|
1987
|
+
ACTION_PREPARATION_NO_PROGRESS_TIMEOUT_MS);
|
|
1988
|
+
}
|
|
1985
1989
|
let earliestStartedAt = Number.POSITIVE_INFINITY;
|
|
1986
1990
|
let latestPositiveProgressAt = 0;
|
|
1987
1991
|
for (const active of activeToolInputs.values()) {
|
|
@@ -1996,9 +2000,74 @@ export async function runAgentLoop(opts) {
|
|
|
1996
2000
|
const progressAt = latestPositiveProgressAt > 0
|
|
1997
2001
|
? latestPositiveProgressAt
|
|
1998
2002
|
: earliestStartedAt;
|
|
1999
|
-
if (
|
|
2000
|
-
|
|
2001
|
-
|
|
2003
|
+
if (Number.isFinite(progressAt)) {
|
|
2004
|
+
deadlineAt = Math.min(deadlineAt, progressAt + ACTION_PREPARATION_NO_PROGRESS_TIMEOUT_MS);
|
|
2005
|
+
}
|
|
2006
|
+
return Number.isFinite(deadlineAt) ? deadlineAt : undefined;
|
|
2007
|
+
};
|
|
2008
|
+
const modelStreamNoProgressDeadlineAt = () => lastModelStreamProgressAt + MODEL_STREAM_NO_PROGRESS_TIMEOUT_MS;
|
|
2009
|
+
const noProgressDeadlineAt = () => {
|
|
2010
|
+
const actionDeadlineAt = actionPreparationDeadlineAt();
|
|
2011
|
+
const modelDeadlineAt = modelStreamNoProgressDeadlineAt();
|
|
2012
|
+
return actionDeadlineAt === undefined
|
|
2013
|
+
? modelDeadlineAt
|
|
2014
|
+
: Math.min(actionDeadlineAt, modelDeadlineAt);
|
|
2015
|
+
};
|
|
2016
|
+
const hasNoProgressStalled = () => Date.now() >= noProgressDeadlineAt();
|
|
2017
|
+
const checkpointNoProgress = () => {
|
|
2018
|
+
if (endedForNoProgress)
|
|
2019
|
+
return;
|
|
2020
|
+
send({
|
|
2021
|
+
type: "auto_continue",
|
|
2022
|
+
reason: "no_progress",
|
|
2023
|
+
});
|
|
2024
|
+
endedForNoProgress = true;
|
|
2025
|
+
};
|
|
2026
|
+
let eventIteratorReturnRequested = false;
|
|
2027
|
+
const requestEventIteratorReturn = (iterator, awaitReturn) => {
|
|
2028
|
+
if (eventIteratorReturnRequested)
|
|
2029
|
+
return;
|
|
2030
|
+
eventIteratorReturnRequested = true;
|
|
2031
|
+
let returnPromise;
|
|
2032
|
+
try {
|
|
2033
|
+
returnPromise = iterator.return?.();
|
|
2034
|
+
}
|
|
2035
|
+
catch {
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
if (!returnPromise)
|
|
2039
|
+
return;
|
|
2040
|
+
if (awaitReturn)
|
|
2041
|
+
return returnPromise.catch(() => undefined);
|
|
2042
|
+
void returnPromise.catch(() => undefined);
|
|
2043
|
+
};
|
|
2044
|
+
const nextEngineEventWithNoProgressTimeout = async (iterator) => {
|
|
2045
|
+
const deadlineAt = noProgressDeadlineAt();
|
|
2046
|
+
const timeoutMs = Math.max(0, deadlineAt - Date.now());
|
|
2047
|
+
if (timeoutMs <= 0) {
|
|
2048
|
+
checkpointNoProgress();
|
|
2049
|
+
requestEventIteratorReturn(iterator, false);
|
|
2050
|
+
return { done: true, value: undefined };
|
|
2051
|
+
}
|
|
2052
|
+
let timeoutId;
|
|
2053
|
+
const next = iterator.next();
|
|
2054
|
+
void next.catch(() => undefined);
|
|
2055
|
+
const timeout = new Promise((resolve) => {
|
|
2056
|
+
timeoutId = setTimeout(() => resolve("timeout"), timeoutMs);
|
|
2057
|
+
});
|
|
2058
|
+
try {
|
|
2059
|
+
const result = await Promise.race([next, timeout]);
|
|
2060
|
+
if (result === "timeout") {
|
|
2061
|
+
checkpointNoProgress();
|
|
2062
|
+
requestEventIteratorReturn(iterator, false);
|
|
2063
|
+
return { done: true, value: undefined };
|
|
2064
|
+
}
|
|
2065
|
+
return result;
|
|
2066
|
+
}
|
|
2067
|
+
finally {
|
|
2068
|
+
if (timeoutId)
|
|
2069
|
+
clearTimeout(timeoutId);
|
|
2070
|
+
}
|
|
2002
2071
|
};
|
|
2003
2072
|
const trackActiveToolInput = (key, toolName, bytes) => {
|
|
2004
2073
|
const now = Date.now();
|
|
@@ -2043,140 +2112,150 @@ export async function runAgentLoop(opts) {
|
|
|
2043
2112
|
now - zeroByteToolInputRestart.firstStartedAt >=
|
|
2044
2113
|
ACTION_PREPARATION_NO_PROGRESS_TIMEOUT_MS);
|
|
2045
2114
|
};
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
}
|
|
2055
|
-
// In-loop processor seam (stream hook). Each chunk is offered to every
|
|
2056
|
-
// processor's `processOutputStream` before the loop handles it. A
|
|
2057
|
-
// processor `abort()` throws a TripWire; catch it locally so it is not
|
|
2058
|
-
// mistaken for a retryable engine error, then break out cleanly.
|
|
2059
|
-
if (processorChain) {
|
|
2060
|
-
try {
|
|
2061
|
-
await processorChain.runStream(event);
|
|
2115
|
+
const eventIterator = eventStream[Symbol.asyncIterator]();
|
|
2116
|
+
let eventIteratorDone = false;
|
|
2117
|
+
try {
|
|
2118
|
+
while (true) {
|
|
2119
|
+
const nextEvent = await nextEngineEventWithNoProgressTimeout(eventIterator);
|
|
2120
|
+
if (nextEvent.done) {
|
|
2121
|
+
eventIteratorDone = true;
|
|
2122
|
+
break;
|
|
2062
2123
|
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2124
|
+
const event = nextEvent.value;
|
|
2125
|
+
if (hasNoProgressStalled()) {
|
|
2126
|
+
checkpointNoProgress();
|
|
2127
|
+
break;
|
|
2128
|
+
}
|
|
2129
|
+
if (event.type !== "gateway-heartbeat") {
|
|
2130
|
+
lastModelStreamProgressAt = Date.now();
|
|
2131
|
+
}
|
|
2132
|
+
// In-loop processor seam (stream hook). Each chunk is offered to every
|
|
2133
|
+
// processor's `processOutputStream` before the loop handles it. A
|
|
2134
|
+
// processor `abort()` throws a TripWire; catch it locally so it is not
|
|
2135
|
+
// mistaken for a retryable engine error, then break out cleanly.
|
|
2136
|
+
if (processorChain) {
|
|
2137
|
+
try {
|
|
2138
|
+
await processorChain.runStream(event);
|
|
2139
|
+
}
|
|
2140
|
+
catch (err) {
|
|
2141
|
+
if (err instanceof TripWire) {
|
|
2142
|
+
emitTripwire(err);
|
|
2143
|
+
break;
|
|
2144
|
+
}
|
|
2145
|
+
throw err;
|
|
2067
2146
|
}
|
|
2068
|
-
throw err;
|
|
2069
2147
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
send({ type: "text", text: event.text });
|
|
2075
|
-
}
|
|
2076
|
-
else if (event.type === "thinking-delta") {
|
|
2077
|
-
thinkingBuffer += event.text;
|
|
2078
|
-
// Forward thinking deltas as a distinct event type so the UI
|
|
2079
|
-
// can render a collapsible "Thinking…" cell while the model
|
|
2080
|
-
// reasons, then collapse it when content arrives.
|
|
2081
|
-
send({ type: "thinking", text: event.text });
|
|
2082
|
-
}
|
|
2083
|
-
else if (event.type === "tool-input-start") {
|
|
2084
|
-
const key = event.id ?? event.name;
|
|
2085
|
-
if (key && event.name) {
|
|
2086
|
-
toolInputNames.set(key, event.name);
|
|
2087
|
-
toolInputBytes.set(key, 0);
|
|
2088
|
-
trackActiveToolInput(key, event.name, 0);
|
|
2148
|
+
if (event.type === "text-delta") {
|
|
2149
|
+
resetZeroByteToolInputRestart();
|
|
2150
|
+
streamedAssistantText += event.text;
|
|
2151
|
+
send({ type: "text", text: event.text });
|
|
2089
2152
|
}
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
});
|
|
2096
|
-
endedForActionPreparationNoProgress = true;
|
|
2097
|
-
break;
|
|
2153
|
+
else if (event.type === "thinking-delta") {
|
|
2154
|
+
thinkingBuffer += event.text;
|
|
2155
|
+
// Forward thinking deltas as a distinct event type so the UI
|
|
2156
|
+
// can render a collapsible "Thinking…" cell while the model
|
|
2157
|
+
// reasons, then collapse it when content arrives.
|
|
2158
|
+
send({ type: "thinking", text: event.text });
|
|
2098
2159
|
}
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2160
|
+
else if (event.type === "tool-input-start") {
|
|
2161
|
+
const key = event.id ?? event.name;
|
|
2162
|
+
if (key && event.name) {
|
|
2163
|
+
toolInputNames.set(key, event.name);
|
|
2164
|
+
toolInputBytes.set(key, 0);
|
|
2165
|
+
trackActiveToolInput(key, event.name, 0);
|
|
2166
|
+
}
|
|
2167
|
+
sendToolInputActivity(event.name, key, undefined, true);
|
|
2168
|
+
if (noteZeroByteToolInputStart(event.name)) {
|
|
2169
|
+
send({
|
|
2170
|
+
type: "auto_continue",
|
|
2171
|
+
reason: "no_progress",
|
|
2172
|
+
});
|
|
2173
|
+
endedForNoProgress = true;
|
|
2174
|
+
break;
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
else if (event.type === "tool-input-delta") {
|
|
2178
|
+
const key = event.id ?? event.name;
|
|
2179
|
+
const toolName = event.name ??
|
|
2180
|
+
(event.id ? toolInputNames.get(event.id) : undefined);
|
|
2181
|
+
let progressBytes;
|
|
2182
|
+
let startedZeroByteInput = false;
|
|
2183
|
+
if (key) {
|
|
2184
|
+
const hadByteRecord = toolInputBytes.has(key);
|
|
2185
|
+
const previous = hadByteRecord
|
|
2186
|
+
? (toolInputBytes.get(key) ?? 0)
|
|
2187
|
+
: 0;
|
|
2188
|
+
progressBytes =
|
|
2189
|
+
previous +
|
|
2190
|
+
new TextEncoder().encode(event.text ?? "").byteLength;
|
|
2191
|
+
toolInputBytes.set(key, progressBytes);
|
|
2192
|
+
if (!hadByteRecord || progressBytes > previous) {
|
|
2193
|
+
trackActiveToolInput(key, toolName, progressBytes);
|
|
2194
|
+
if (progressBytes > 0) {
|
|
2195
|
+
resetZeroByteToolInputRestart();
|
|
2196
|
+
}
|
|
2197
|
+
else if (!hadByteRecord) {
|
|
2198
|
+
startedZeroByteInput = true;
|
|
2199
|
+
}
|
|
2122
2200
|
}
|
|
2123
2201
|
}
|
|
2202
|
+
sendToolInputActivity(toolName, key, progressBytes);
|
|
2203
|
+
if (startedZeroByteInput &&
|
|
2204
|
+
toolName &&
|
|
2205
|
+
noteZeroByteToolInputStart(toolName)) {
|
|
2206
|
+
send({
|
|
2207
|
+
type: "auto_continue",
|
|
2208
|
+
reason: "no_progress",
|
|
2209
|
+
});
|
|
2210
|
+
endedForNoProgress = true;
|
|
2211
|
+
break;
|
|
2212
|
+
}
|
|
2124
2213
|
}
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
toolName &&
|
|
2128
|
-
noteZeroByteToolInputStart(toolName)) {
|
|
2129
|
-
send({
|
|
2130
|
-
type: "auto_continue",
|
|
2131
|
-
reason: "no_progress",
|
|
2132
|
-
});
|
|
2133
|
-
endedForActionPreparationNoProgress = true;
|
|
2134
|
-
break;
|
|
2214
|
+
else if (event.type === "gateway-heartbeat") {
|
|
2215
|
+
send({ type: "stream_keepalive" });
|
|
2135
2216
|
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
toolCallErrors.set(event.id, {
|
|
2145
|
-
name: event.name,
|
|
2146
|
-
input: event.input,
|
|
2147
|
-
error: event.error,
|
|
2148
|
-
});
|
|
2149
|
-
}
|
|
2150
|
-
else if (event.type === "assistant-content") {
|
|
2151
|
-
assistantContent = event.parts;
|
|
2152
|
-
}
|
|
2153
|
-
else if (event.type === "usage") {
|
|
2154
|
-
usage.inputTokens += event.inputTokens;
|
|
2155
|
-
usage.outputTokens += event.outputTokens;
|
|
2156
|
-
usage.cacheReadTokens += event.cacheReadTokens ?? 0;
|
|
2157
|
-
usage.cacheWriteTokens += event.cacheWriteTokens ?? 0;
|
|
2158
|
-
}
|
|
2159
|
-
else if (event.type === "stop") {
|
|
2160
|
-
terminalStopReason = event.reason;
|
|
2161
|
-
if (event.reason === "error") {
|
|
2162
|
-
throw new EngineError(event.error ?? "Engine stream error", {
|
|
2163
|
-
errorCode: event.errorCode,
|
|
2164
|
-
upgradeUrl: event.upgradeUrl,
|
|
2165
|
-
statusCode: event.statusCode,
|
|
2166
|
-
providerRetryable: event.providerRetryable,
|
|
2217
|
+
else if (event.type === "tool-call") {
|
|
2218
|
+
// The authoritative tool-call blocks arrive in assistant-content.
|
|
2219
|
+
}
|
|
2220
|
+
else if (event.type === "tool-call-error") {
|
|
2221
|
+
toolCallErrors.set(event.id, {
|
|
2222
|
+
name: event.name,
|
|
2223
|
+
input: event.input,
|
|
2224
|
+
error: event.error,
|
|
2167
2225
|
});
|
|
2168
2226
|
}
|
|
2227
|
+
else if (event.type === "assistant-content") {
|
|
2228
|
+
assistantContent = event.parts;
|
|
2229
|
+
}
|
|
2230
|
+
else if (event.type === "usage") {
|
|
2231
|
+
usage.inputTokens += event.inputTokens;
|
|
2232
|
+
usage.outputTokens += event.outputTokens;
|
|
2233
|
+
usage.cacheReadTokens += event.cacheReadTokens ?? 0;
|
|
2234
|
+
usage.cacheWriteTokens += event.cacheWriteTokens ?? 0;
|
|
2235
|
+
}
|
|
2236
|
+
else if (event.type === "stop") {
|
|
2237
|
+
terminalStopReason = event.reason;
|
|
2238
|
+
if (event.reason === "error") {
|
|
2239
|
+
throw new EngineError(event.error ?? "Engine stream error", {
|
|
2240
|
+
errorCode: event.errorCode,
|
|
2241
|
+
upgradeUrl: event.upgradeUrl,
|
|
2242
|
+
statusCode: event.statusCode,
|
|
2243
|
+
providerRetryable: event.providerRetryable,
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
if (hasNoProgressStalled()) {
|
|
2248
|
+
checkpointNoProgress();
|
|
2249
|
+
break;
|
|
2250
|
+
}
|
|
2169
2251
|
}
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
});
|
|
2175
|
-
endedForActionPreparationNoProgress = true;
|
|
2176
|
-
break;
|
|
2252
|
+
}
|
|
2253
|
+
finally {
|
|
2254
|
+
if (!eventIteratorDone) {
|
|
2255
|
+
await requestEventIteratorReturn(eventIterator, !eventIteratorReturnRequested);
|
|
2177
2256
|
}
|
|
2178
2257
|
}
|
|
2179
|
-
if (
|
|
2258
|
+
if (endedForNoProgress) {
|
|
2180
2259
|
return usage;
|
|
2181
2260
|
}
|
|
2182
2261
|
break;
|