@nexrall/code-core 1.4.56 → 1.4.57
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/dist/agent/loop.d.ts +26 -0
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +96 -3
- package/package.json +1 -1
package/dist/agent/loop.d.ts
CHANGED
|
@@ -2,6 +2,32 @@ import type { Message, ToolResult, AgentLoopOptions, EnvContext } from '../types
|
|
|
2
2
|
import { type AgentType, type AgentMemoryScope } from './agentTypes';
|
|
3
3
|
import { bashNeedsRepoLock } from '../permissions/bashClassify';
|
|
4
4
|
export { bashNeedsRepoLock };
|
|
5
|
+
/** Backoff for the Nth (0-based) empty-turn retry. Pure, so the schedule is testable. */
|
|
6
|
+
export declare function emptyTurnBackoffMs(attempt: number): number;
|
|
7
|
+
/**
|
|
8
|
+
* Should an empty assistant turn be retried automatically, rather than surfaced?
|
|
9
|
+
*
|
|
10
|
+
* Split out as a pure function because the two "empty" causes look IDENTICAL at the
|
|
11
|
+
* call site (both are `content.length === 0`) and telling them apart is the entire
|
|
12
|
+
* point — the previous bug in this area was treating a deterministic truncation as a
|
|
13
|
+
* transient hiccup and advising a retry that reproduced it verbatim.
|
|
14
|
+
*
|
|
15
|
+
* • `max_tokens` → the model burned its whole output budget on reasoning and was CUT
|
|
16
|
+
* OFF. Retrying re-runs the same prompt with the same budget and the same reasoning
|
|
17
|
+
* behaviour, so it fails the same way while billing again. Never retried; the user
|
|
18
|
+
* is told to lower effort or split the task (stopReasonNotice 'output-limit').
|
|
19
|
+
* • anything else → a genuinely transient empty turn. Retried, up to the limit.
|
|
20
|
+
*
|
|
21
|
+
* The attempt cap matters as much as the classification: a model that has decided to
|
|
22
|
+
* return nothing (e.g. a prompt it refuses to continue) would otherwise loop forever
|
|
23
|
+
* on a paid endpoint. After the cap we fall back to the old visible notice.
|
|
24
|
+
*/
|
|
25
|
+
export declare function shouldRetryEmptyTurn(stopReason: string | null | undefined, attemptsSoFar: number): boolean;
|
|
26
|
+
/** Empty-turn retry limits, exposed for tests. */
|
|
27
|
+
export declare const _emptyTurnRetry: {
|
|
28
|
+
EMPTY_TURN_RETRY_LIMIT: number;
|
|
29
|
+
EMPTY_TURN_RETRY_BASE_MS: number;
|
|
30
|
+
};
|
|
5
31
|
/**
|
|
6
32
|
* Fingerprint one round's tool failures, for the repeated-failure runaway guard.
|
|
7
33
|
*
|
package/dist/agent/loop.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAKP,UAAU,EACV,gBAAgB,EAChB,UAAU,EACX,MAAM,UAAU,CAAC;AAIlB,OAAO,EAA8D,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMjI,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/loop.js
CHANGED
|
@@ -33,7 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = exports.ToolNotAllowedError = exports.AGENT_MEMORY_TOOL_SCHEMA = exports.AGENT_MEMORY_TOOL = exports._stallLimits = exports.bashNeedsRepoLock = void 0;
|
|
36
|
+
exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = exports.ToolNotAllowedError = exports.AGENT_MEMORY_TOOL_SCHEMA = exports.AGENT_MEMORY_TOOL = exports._stallLimits = exports._emptyTurnRetry = exports.bashNeedsRepoLock = void 0;
|
|
37
|
+
exports.emptyTurnBackoffMs = emptyTurnBackoffMs;
|
|
38
|
+
exports.shouldRetryEmptyTurn = shouldRetryEmptyTurn;
|
|
37
39
|
exports.errorRoundSignature = errorRoundSignature;
|
|
38
40
|
exports.executeAgentMemoryWrite = executeAgentMemoryWrite;
|
|
39
41
|
exports.stopReasonNotice = stopReasonNotice;
|
|
@@ -226,6 +228,60 @@ const STALL_LIMIT = 8; // consecutive all-failed tool rounds → give up (runawa
|
|
|
226
228
|
// being stuck than a total failure: legitimately retrying one failing command a few times
|
|
227
229
|
// while making progress elsewhere is normal, twelve times is not.
|
|
228
230
|
const REPEAT_STALL_LIMIT = 12;
|
|
231
|
+
// ─── Empty-turn auto-retry ────────────────────────────────────────────────────
|
|
232
|
+
//
|
|
233
|
+
// A model turn can come back STRUCTURALLY FINE (the stream completed, `message_complete`
|
|
234
|
+
// arrived) and still contain nothing usable once thinking blocks are stripped for
|
|
235
|
+
// history. Anthropic models hit this materially more often than the OpenAI-compatible
|
|
236
|
+
// ones, because only they emit `thinking`/`redacted_thinking` blocks — a turn that
|
|
237
|
+
// reasons and then ends without committing text or a tool call strips down to `[]`.
|
|
238
|
+
//
|
|
239
|
+
// The transport layer cannot fix this. client.ts only retries when the stream produced
|
|
240
|
+
// NOTHING; here the stream produced a valid message that happens to be empty, so it
|
|
241
|
+
// resolves normally and the loop used to break immediately and tell the user to type
|
|
242
|
+
// "continue" — asking a human to press a button the loop can press itself.
|
|
243
|
+
//
|
|
244
|
+
// Retrying here is safe for a specific, checkable reason, not a hopeful one:
|
|
245
|
+
// • tools are dispatched from `assistantMessage.content` AFTER this check, and this
|
|
246
|
+
// content is empty, so no tool ran and no file changed;
|
|
247
|
+
// • nothing was pushed to `messages` (the push happens below this branch), so the
|
|
248
|
+
// history is byte-identical to the previous attempt — a retry is a clean re-ask,
|
|
249
|
+
// not a resend of a corrupted turn;
|
|
250
|
+
// • no round was counted, no hook fired, no checkpoint was taken.
|
|
251
|
+
// Contrast the output-limit case, which is NOT retried: see shouldRetryEmptyTurn.
|
|
252
|
+
const EMPTY_TURN_RETRY_LIMIT = 3;
|
|
253
|
+
// Short, escalating pause (1s, 2s, 4s). Long enough to ride out the upstream blip that
|
|
254
|
+
// causes this; short enough that three failures cost ~7s rather than a visible stall.
|
|
255
|
+
const EMPTY_TURN_RETRY_BASE_MS = 1000;
|
|
256
|
+
/** Backoff for the Nth (0-based) empty-turn retry. Pure, so the schedule is testable. */
|
|
257
|
+
function emptyTurnBackoffMs(attempt) {
|
|
258
|
+
return EMPTY_TURN_RETRY_BASE_MS * Math.pow(2, Math.max(0, attempt));
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Should an empty assistant turn be retried automatically, rather than surfaced?
|
|
262
|
+
*
|
|
263
|
+
* Split out as a pure function because the two "empty" causes look IDENTICAL at the
|
|
264
|
+
* call site (both are `content.length === 0`) and telling them apart is the entire
|
|
265
|
+
* point — the previous bug in this area was treating a deterministic truncation as a
|
|
266
|
+
* transient hiccup and advising a retry that reproduced it verbatim.
|
|
267
|
+
*
|
|
268
|
+
* • `max_tokens` → the model burned its whole output budget on reasoning and was CUT
|
|
269
|
+
* OFF. Retrying re-runs the same prompt with the same budget and the same reasoning
|
|
270
|
+
* behaviour, so it fails the same way while billing again. Never retried; the user
|
|
271
|
+
* is told to lower effort or split the task (stopReasonNotice 'output-limit').
|
|
272
|
+
* • anything else → a genuinely transient empty turn. Retried, up to the limit.
|
|
273
|
+
*
|
|
274
|
+
* The attempt cap matters as much as the classification: a model that has decided to
|
|
275
|
+
* return nothing (e.g. a prompt it refuses to continue) would otherwise loop forever
|
|
276
|
+
* on a paid endpoint. After the cap we fall back to the old visible notice.
|
|
277
|
+
*/
|
|
278
|
+
function shouldRetryEmptyTurn(stopReason, attemptsSoFar) {
|
|
279
|
+
if (stopReason === 'max_tokens')
|
|
280
|
+
return false;
|
|
281
|
+
return attemptsSoFar < EMPTY_TURN_RETRY_LIMIT;
|
|
282
|
+
}
|
|
283
|
+
/** Empty-turn retry limits, exposed for tests. */
|
|
284
|
+
exports._emptyTurnRetry = { EMPTY_TURN_RETRY_LIMIT, EMPTY_TURN_RETRY_BASE_MS };
|
|
229
285
|
/**
|
|
230
286
|
* Fingerprint one round's tool failures, for the repeated-failure runaway guard.
|
|
231
287
|
*
|
|
@@ -331,9 +387,14 @@ function stopReasonNotice(reason, ctx = {}) {
|
|
|
331
387
|
// 'aborted' — keeps "the user cancelled" from silently coming to mean two things.
|
|
332
388
|
case 'reported-elsewhere':
|
|
333
389
|
return null;
|
|
390
|
+
// Reaching this notice now means the loop ALREADY retried automatically and the
|
|
391
|
+
// model came back empty every time (see shouldRetryEmptyTurn). Saying "send
|
|
392
|
+
// continue to retry" without that context reads as if nothing was tried, and the
|
|
393
|
+
// user's manual retry is then the fourth identical attempt — so name the attempts.
|
|
334
394
|
case 'empty-response':
|
|
335
|
-
return `\n\u26a0\ufe0f The model returned an empty response, so nothing was done.
|
|
336
|
-
`upstream hiccup
|
|
395
|
+
return `\n\u26a0\ufe0f The model returned an empty response ${EMPTY_TURN_RETRY_LIMIT} times in a row, so nothing was done. ` +
|
|
396
|
+
`This is usually a transient upstream hiccup that the agent retries by itself; it did not clear this time. ` +
|
|
397
|
+
`Send "continue" to try again, or switch model with /model if it persists.\n`;
|
|
337
398
|
// Deliberately NOT folded into 'empty-response'. Both arrive as an assistant
|
|
338
399
|
// message with no content once thinking blocks are stripped, so they used to
|
|
339
400
|
// be indistinguishable — and the user was told the truncation case was "a
|
|
@@ -2565,6 +2626,10 @@ async function runAgentLoop(initialMessages, options) {
|
|
|
2565
2626
|
let stalledRepeatError = null;
|
|
2566
2627
|
let budget = maxIterations; // extended by auto-continue, capped at hardCap
|
|
2567
2628
|
let iteration = 0;
|
|
2629
|
+
// Consecutive empty (thinking-only) model turns auto-retried in this run. Reset on
|
|
2630
|
+
// any turn that produces content, so a session that hits one blip an hour keeps its
|
|
2631
|
+
// full retry budget each time instead of slowly using it up over a long run.
|
|
2632
|
+
let emptyTurnRetries = 0;
|
|
2568
2633
|
// ─── Verification nudge (GAP D) ───────────────────────────────────────────────
|
|
2569
2634
|
// Coding agents commonly claim "done" after editing files without ever running a
|
|
2570
2635
|
// build/test/lint command to confirm the change actually works (the industry's
|
|
@@ -2953,10 +3018,38 @@ async function runAgentLoop(initialMessages, options) {
|
|
|
2953
3018
|
// 'max_tokens' means the model was CUT OFF at its output ceiling. Landing here
|
|
2954
3019
|
// with empty content means the entire budget went to thinking blocks that were
|
|
2955
3020
|
// just stripped, leaving nothing to show. Deterministic, not a hiccup.
|
|
3021
|
+
//
|
|
3022
|
+
// …and before giving up at all, RETRY IT. Nothing has been pushed to `messages`,
|
|
3023
|
+
// no tool ran (tools are dispatched from this same empty content, below), so the
|
|
3024
|
+
// next attempt re-asks the identical prompt with no duplicated side effects. This
|
|
3025
|
+
// is the fix for users being told to type "continue" for a hiccup the loop can
|
|
3026
|
+
// ride out itself; shouldRetryEmptyTurn keeps the deterministic `max_tokens`
|
|
3027
|
+
// truncation OUT of it, because retrying that reproduces it exactly.
|
|
3028
|
+
if (shouldRetryEmptyTurn(assistantMessage.stopReason, emptyTurnRetries)) {
|
|
3029
|
+
const waitMs = emptyTurnBackoffMs(emptyTurnRetries);
|
|
3030
|
+
emptyTurnRetries++;
|
|
3031
|
+
// Reuse the existing "reconnecting…" channel rather than printing a line into
|
|
3032
|
+
// the transcript: this is the same class of event (a transparent retry the user
|
|
3033
|
+
// does not have to act on), and clients already render + auto-clear it.
|
|
3034
|
+
options.onRetry?.(emptyTurnRetries, EMPTY_TURN_RETRY_LIMIT, 'The model returned an empty response — retrying automatically');
|
|
3035
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
3036
|
+
if (options.abortSignal?.aborted) {
|
|
3037
|
+
stopReason = 'aborted';
|
|
3038
|
+
break;
|
|
3039
|
+
}
|
|
3040
|
+
options.onRetryResolved?.();
|
|
3041
|
+
// Does NOT count as an iteration: a turn that produced nothing must not eat
|
|
3042
|
+
// the step budget, or three blips would silently shorten the task by three
|
|
3043
|
+
// steps. `continue` re-enters the loop body without incrementing.
|
|
3044
|
+
iteration--;
|
|
3045
|
+
continue;
|
|
3046
|
+
}
|
|
2956
3047
|
runSimpleHooks(hooks.PostMessageComplete, options.workDir);
|
|
2957
3048
|
stopReason = assistantMessage.stopReason === 'max_tokens' ? 'output-limit' : 'empty-response';
|
|
2958
3049
|
break;
|
|
2959
3050
|
}
|
|
3051
|
+
// A turn with real content: the blip (if any) is over, restore the full budget.
|
|
3052
|
+
emptyTurnRetries = 0;
|
|
2960
3053
|
// stopReason is transient, single-turn metadata for the truncated-tool-call guard
|
|
2961
3054
|
// below — it must never be replayed to the API as part of message history (Anthropic
|
|
2962
3055
|
// rejects unknown fields on a request message with a 400).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexrall/code-core",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.57",
|
|
4
4
|
"description": "Core agent loop, tools, and extension primitives for Nexrall Code \u2014 embed an AI coding agent in any Node.js application.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nexrall <support@nexrall.com> (https://nexrall.com)",
|