@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.
@@ -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 endedForActionPreparationNoProgress = false;
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 hasActionPreparationStalled = () => {
1982
- if (activeToolInputs.size === 0)
1983
- return false;
1984
- const now = Date.now();
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 (!Number.isFinite(progressAt))
2000
- return false;
2001
- return now - progressAt >= ACTION_PREPARATION_NO_PROGRESS_TIMEOUT_MS;
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
- for await (const event of eventStream) {
2047
- if (hasActionPreparationStalled()) {
2048
- send({
2049
- type: "auto_continue",
2050
- reason: "no_progress",
2051
- });
2052
- endedForActionPreparationNoProgress = true;
2053
- break;
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
- catch (err) {
2064
- if (err instanceof TripWire) {
2065
- emitTripwire(err);
2066
- break;
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
- if (event.type === "text-delta") {
2072
- resetZeroByteToolInputRestart();
2073
- streamedAssistantText += event.text;
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
- sendToolInputActivity(event.name, key, undefined, true);
2091
- if (noteZeroByteToolInputStart(event.name)) {
2092
- send({
2093
- type: "auto_continue",
2094
- reason: "no_progress",
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
- else if (event.type === "tool-input-delta") {
2101
- const key = event.id ?? event.name;
2102
- const toolName = event.name ??
2103
- (event.id ? toolInputNames.get(event.id) : undefined);
2104
- let progressBytes;
2105
- let startedZeroByteInput = false;
2106
- if (key) {
2107
- const hadByteRecord = toolInputBytes.has(key);
2108
- const previous = hadByteRecord
2109
- ? (toolInputBytes.get(key) ?? 0)
2110
- : 0;
2111
- progressBytes =
2112
- previous +
2113
- new TextEncoder().encode(event.text ?? "").byteLength;
2114
- toolInputBytes.set(key, progressBytes);
2115
- if (!hadByteRecord || progressBytes > previous) {
2116
- trackActiveToolInput(key, toolName, progressBytes);
2117
- if (progressBytes > 0) {
2118
- resetZeroByteToolInputRestart(toolName);
2119
- }
2120
- else if (!hadByteRecord) {
2121
- startedZeroByteInput = true;
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
- sendToolInputActivity(toolName, key, progressBytes);
2126
- if (startedZeroByteInput &&
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
- else if (event.type === "gateway-heartbeat") {
2138
- send({ type: "stream_keepalive" });
2139
- }
2140
- else if (event.type === "tool-call") {
2141
- // The authoritative tool-call blocks arrive in assistant-content.
2142
- }
2143
- else if (event.type === "tool-call-error") {
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
- if (hasActionPreparationStalled()) {
2171
- send({
2172
- type: "auto_continue",
2173
- reason: "no_progress",
2174
- });
2175
- endedForActionPreparationNoProgress = true;
2176
- break;
2252
+ }
2253
+ finally {
2254
+ if (!eventIteratorDone) {
2255
+ await requestEventIteratorReturn(eventIterator, !eventIteratorReturnRequested);
2177
2256
  }
2178
2257
  }
2179
- if (endedForActionPreparationNoProgress) {
2258
+ if (endedForNoProgress) {
2180
2259
  return usage;
2181
2260
  }
2182
2261
  break;