nexrall-code 0.5.50 → 0.5.52
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/index.js +689 -236
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -10234,248 +10234,254 @@ var require_client = __commonJS({
|
|
|
10234
10234
|
};
|
|
10235
10235
|
const haveCompleteMessage = () => completedMessage !== null;
|
|
10236
10236
|
const partialInputs = {};
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
}
|
|
10253
|
-
if (now - lastDataAt > HEARTBEAT_TIMEOUT_MS) {
|
|
10254
|
-
clearInterval(heartbeatWatchdog);
|
|
10255
|
-
stream.destroy?.();
|
|
10256
|
-
const recoverable = !emittedToCaller || !!allowRestartAfterRender;
|
|
10257
|
-
const secs = Math.round(HEARTBEAT_TIMEOUT_MS / 1e3);
|
|
10258
|
-
reject(tagTransient(new Error(recoverable ? `Connection lost \u2014 no data received for ${secs} s. Reconnecting\u2026` : `Connection lost \u2014 no data received for ${secs} s. Retry your message.`)));
|
|
10259
|
-
return;
|
|
10260
|
-
}
|
|
10261
|
-
const stallLimitMs = sawModelEvent ? PROGRESS_TIMEOUT_MS : FIRST_EVENT_TIMEOUT_MS;
|
|
10262
|
-
if (now - lastProgressAt > stallLimitMs) {
|
|
10263
|
-
clearInterval(heartbeatWatchdog);
|
|
10264
|
-
stream.destroy?.();
|
|
10265
|
-
if (haveCompleteMessage()) {
|
|
10237
|
+
let heartbeatWatchdog;
|
|
10238
|
+
try {
|
|
10239
|
+
await new Promise((resolve3, reject) => {
|
|
10240
|
+
const stream = response.body;
|
|
10241
|
+
const HEARTBEAT_TIMEOUT_MS = 9e4;
|
|
10242
|
+
const FIRST_EVENT_TIMEOUT_MS = Number(process.env.NEXRALL_FIRST_EVENT_TIMEOUT_MS) || 3e5;
|
|
10243
|
+
const PROGRESS_TIMEOUT_MS = Number(process.env.NEXRALL_PROGRESS_TIMEOUT_MS) || 15e4;
|
|
10244
|
+
let sawModelEvent = false;
|
|
10245
|
+
let lastDataAt = Date.now();
|
|
10246
|
+
let lastProgressAt = Date.now();
|
|
10247
|
+
heartbeatWatchdog = setInterval(() => {
|
|
10248
|
+
const now = Date.now();
|
|
10249
|
+
if (haveCompleteMessage() && now - lastDataAt > HEARTBEAT_TIMEOUT_MS) {
|
|
10250
|
+
clearInterval(heartbeatWatchdog);
|
|
10251
|
+
stream.destroy?.();
|
|
10266
10252
|
resolve3();
|
|
10267
10253
|
return;
|
|
10268
10254
|
}
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
if (event.type !== "event") {
|
|
10276
|
-
return;
|
|
10277
|
-
}
|
|
10278
|
-
{
|
|
10279
|
-
const frameId = event.id;
|
|
10280
|
-
if (frameId) {
|
|
10281
|
-
const n = Number(frameId);
|
|
10282
|
-
if (Number.isFinite(n) && n > lastEventId)
|
|
10283
|
-
lastEventId = n;
|
|
10284
|
-
}
|
|
10285
|
-
const raw = event.data;
|
|
10286
|
-
if (!raw || raw === "[DONE]") {
|
|
10287
|
-
resolve3();
|
|
10255
|
+
if (now - lastDataAt > HEARTBEAT_TIMEOUT_MS) {
|
|
10256
|
+
clearInterval(heartbeatWatchdog);
|
|
10257
|
+
stream.destroy?.();
|
|
10258
|
+
const recoverable = !emittedToCaller || !!allowRestartAfterRender;
|
|
10259
|
+
const secs = Math.round(HEARTBEAT_TIMEOUT_MS / 1e3);
|
|
10260
|
+
reject(tagTransient(new Error(recoverable ? `Connection lost \u2014 no data received for ${secs} s. Reconnecting\u2026` : `Connection lost \u2014 no data received for ${secs} s. Retry your message.`)));
|
|
10288
10261
|
return;
|
|
10289
10262
|
}
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10263
|
+
const stallLimitMs = sawModelEvent ? PROGRESS_TIMEOUT_MS : FIRST_EVENT_TIMEOUT_MS;
|
|
10264
|
+
if (now - lastProgressAt > stallLimitMs) {
|
|
10265
|
+
clearInterval(heartbeatWatchdog);
|
|
10266
|
+
stream.destroy?.();
|
|
10267
|
+
if (haveCompleteMessage()) {
|
|
10268
|
+
resolve3();
|
|
10269
|
+
return;
|
|
10270
|
+
}
|
|
10271
|
+
reject(tagTransient(new Error(sawModelEvent ? `The model stopped responding mid-stream (no output for ${Math.round(stallLimitMs / 1e3)} s).` : `The model did not start responding within ${Math.round(stallLimitMs / 1e3)} s (large context can take a while to process).`)));
|
|
10294
10272
|
return;
|
|
10295
10273
|
}
|
|
10296
|
-
|
|
10274
|
+
}, 5e3);
|
|
10275
|
+
const parser = (0, eventsource_parser_1.createParser)((event) => {
|
|
10276
|
+
lastDataAt = Date.now();
|
|
10277
|
+
if (event.type !== "event") {
|
|
10297
10278
|
return;
|
|
10298
10279
|
}
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
const text = typeof evt.text === "string" ? evt.text : "";
|
|
10306
|
-
textParts.push(text);
|
|
10307
|
-
emittedToCaller = true;
|
|
10308
|
-
emittedChars += text.length;
|
|
10309
|
-
emittedAnythingAcrossAttempts = true;
|
|
10310
|
-
emittedCharsAcrossAttempts += text.length;
|
|
10311
|
-
onEvent({ type: "text", text });
|
|
10312
|
-
break;
|
|
10280
|
+
{
|
|
10281
|
+
const frameId = event.id;
|
|
10282
|
+
if (frameId) {
|
|
10283
|
+
const n = Number(frameId);
|
|
10284
|
+
if (Number.isFinite(n) && n > lastEventId)
|
|
10285
|
+
lastEventId = n;
|
|
10313
10286
|
}
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
name: typeof evt.name === "string" ? evt.name : "",
|
|
10319
|
-
input: evt.input ?? {}
|
|
10320
|
-
};
|
|
10321
|
-
if (typeof evt.input_json_delta === "string") {
|
|
10322
|
-
partialInputs[block.id] = (partialInputs[block.id] ?? "") + evt.input_json_delta;
|
|
10323
|
-
try {
|
|
10324
|
-
block.input = JSON.parse(partialInputs[block.id]);
|
|
10325
|
-
} catch {
|
|
10326
|
-
return;
|
|
10327
|
-
}
|
|
10328
|
-
}
|
|
10329
|
-
toolUseBlocks.push(block);
|
|
10330
|
-
emittedToCaller = true;
|
|
10331
|
-
emittedAnythingAcrossAttempts = true;
|
|
10332
|
-
onEvent({ type: "tool_use", id: block.id, name: block.name, input: block.input });
|
|
10333
|
-
break;
|
|
10334
|
-
}
|
|
10335
|
-
case "message_complete": {
|
|
10336
|
-
const nestedMessage = evt.message;
|
|
10337
|
-
const stopReason = typeof nestedMessage?.stop_reason === "string" ? nestedMessage.stop_reason : null;
|
|
10338
|
-
const contentBlocks2 = [];
|
|
10339
|
-
const fullText2 = textParts.join("");
|
|
10340
|
-
if (fullText2) {
|
|
10341
|
-
contentBlocks2.push({ type: "text", text: fullText2 });
|
|
10342
|
-
}
|
|
10343
|
-
contentBlocks2.push(...toolUseBlocks);
|
|
10344
|
-
const rawContent = Array.isArray(nestedMessage?.content) ? nestedMessage.content : null;
|
|
10345
|
-
const finalContent = chooseFinalContent(contentBlocks2, rawContent);
|
|
10346
|
-
completedMessage = {
|
|
10347
|
-
role: "assistant",
|
|
10348
|
-
content: finalContent,
|
|
10349
|
-
stopReason
|
|
10350
|
-
};
|
|
10351
|
-
onEvent({ type: "message_complete", message: completedMessage });
|
|
10352
|
-
break;
|
|
10287
|
+
const raw = event.data;
|
|
10288
|
+
if (!raw || raw === "[DONE]") {
|
|
10289
|
+
resolve3();
|
|
10290
|
+
return;
|
|
10353
10291
|
}
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
// billed but never completed (its stream `abort` path). Without this
|
|
10360
|
-
// the flag dies here and every consumer that sums usage silently folds
|
|
10361
|
-
// a discarded attempt's tokens into the successful turn's total.
|
|
10362
|
-
...evt.partial === true ? { partial: true } : {},
|
|
10363
|
-
// `replayed` means these tokens are being reported a SECOND time: the
|
|
10364
|
-
// turn completed and was billed on an earlier attempt whose `done` never
|
|
10365
|
-
// reached us, and the backend served this one from its idempotency cache
|
|
10366
|
-
// instead of re-running the model. Nothing new was charged, so a cost
|
|
10367
|
-
// display must not add them again.
|
|
10368
|
-
...evt.replayed === true ? { replayed: true } : {},
|
|
10369
|
-
usage: {
|
|
10370
|
-
input_tokens: evt.input_tokens,
|
|
10371
|
-
output_tokens: evt.output_tokens,
|
|
10372
|
-
cache_creation_input_tokens: typeof evt.cache_creation_input_tokens === "number" ? evt.cache_creation_input_tokens : void 0,
|
|
10373
|
-
cache_read_input_tokens: typeof evt.cache_read_input_tokens === "number" ? evt.cache_read_input_tokens : void 0
|
|
10374
|
-
}
|
|
10375
|
-
});
|
|
10376
|
-
}
|
|
10377
|
-
break;
|
|
10292
|
+
let parsed;
|
|
10293
|
+
try {
|
|
10294
|
+
parsed = JSON.parse(raw);
|
|
10295
|
+
} catch {
|
|
10296
|
+
return;
|
|
10378
10297
|
}
|
|
10379
|
-
|
|
10380
|
-
|
|
10298
|
+
if (typeof parsed !== "object" || parsed === null || !("type" in parsed)) {
|
|
10299
|
+
return;
|
|
10381
10300
|
}
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10301
|
+
const evt = parsed;
|
|
10302
|
+
lastProgressAt = Date.now();
|
|
10303
|
+
sawModelEvent = true;
|
|
10304
|
+
clearRetryIfNeeded();
|
|
10305
|
+
switch (evt.type) {
|
|
10306
|
+
case "text": {
|
|
10307
|
+
const text = typeof evt.text === "string" ? evt.text : "";
|
|
10308
|
+
textParts.push(text);
|
|
10385
10309
|
emittedToCaller = true;
|
|
10386
10310
|
emittedChars += text.length;
|
|
10387
10311
|
emittedAnythingAcrossAttempts = true;
|
|
10388
10312
|
emittedCharsAcrossAttempts += text.length;
|
|
10389
|
-
onEvent({ type: "
|
|
10313
|
+
onEvent({ type: "text", text });
|
|
10314
|
+
break;
|
|
10390
10315
|
}
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
|
|
10400
|
-
|
|
10316
|
+
case "tool_use": {
|
|
10317
|
+
const block = {
|
|
10318
|
+
type: "tool_use",
|
|
10319
|
+
id: typeof evt.id === "string" ? evt.id : "",
|
|
10320
|
+
name: typeof evt.name === "string" ? evt.name : "",
|
|
10321
|
+
input: evt.input ?? {}
|
|
10322
|
+
};
|
|
10323
|
+
if (typeof evt.input_json_delta === "string") {
|
|
10324
|
+
partialInputs[block.id] = (partialInputs[block.id] ?? "") + evt.input_json_delta;
|
|
10325
|
+
try {
|
|
10326
|
+
block.input = JSON.parse(partialInputs[block.id]);
|
|
10327
|
+
} catch {
|
|
10328
|
+
return;
|
|
10329
|
+
}
|
|
10330
|
+
}
|
|
10331
|
+
toolUseBlocks.push(block);
|
|
10401
10332
|
emittedToCaller = true;
|
|
10402
|
-
emittedChars += text.length;
|
|
10403
10333
|
emittedAnythingAcrossAttempts = true;
|
|
10404
|
-
|
|
10405
|
-
|
|
10334
|
+
onEvent({ type: "tool_use", id: block.id, name: block.name, input: block.input });
|
|
10335
|
+
break;
|
|
10406
10336
|
}
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10419
|
-
|
|
10420
|
-
|
|
10421
|
-
|
|
10422
|
-
|
|
10423
|
-
|
|
10424
|
-
|
|
10425
|
-
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
|
|
10429
|
-
|
|
10337
|
+
case "message_complete": {
|
|
10338
|
+
const nestedMessage = evt.message;
|
|
10339
|
+
const stopReason = typeof nestedMessage?.stop_reason === "string" ? nestedMessage.stop_reason : null;
|
|
10340
|
+
const contentBlocks2 = [];
|
|
10341
|
+
const fullText2 = textParts.join("");
|
|
10342
|
+
if (fullText2) {
|
|
10343
|
+
contentBlocks2.push({ type: "text", text: fullText2 });
|
|
10344
|
+
}
|
|
10345
|
+
contentBlocks2.push(...toolUseBlocks);
|
|
10346
|
+
const rawContent = Array.isArray(nestedMessage?.content) ? nestedMessage.content : null;
|
|
10347
|
+
const finalContent = chooseFinalContent(contentBlocks2, rawContent);
|
|
10348
|
+
completedMessage = {
|
|
10349
|
+
role: "assistant",
|
|
10350
|
+
content: finalContent,
|
|
10351
|
+
stopReason
|
|
10352
|
+
};
|
|
10353
|
+
onEvent({ type: "message_complete", message: completedMessage });
|
|
10354
|
+
break;
|
|
10355
|
+
}
|
|
10356
|
+
case "usage": {
|
|
10357
|
+
if (typeof evt.input_tokens === "number" && typeof evt.output_tokens === "number") {
|
|
10358
|
+
onEvent({
|
|
10359
|
+
type: "usage",
|
|
10360
|
+
// Forwarded, not dropped: the backend tags the usage of an attempt it
|
|
10361
|
+
// billed but never completed (its stream `abort` path). Without this
|
|
10362
|
+
// the flag dies here and every consumer that sums usage silently folds
|
|
10363
|
+
// a discarded attempt's tokens into the successful turn's total.
|
|
10364
|
+
...evt.partial === true ? { partial: true } : {},
|
|
10365
|
+
// `replayed` means these tokens are being reported a SECOND time: the
|
|
10366
|
+
// turn completed and was billed on an earlier attempt whose `done` never
|
|
10367
|
+
// reached us, and the backend served this one from its idempotency cache
|
|
10368
|
+
// instead of re-running the model. Nothing new was charged, so a cost
|
|
10369
|
+
// display must not add them again.
|
|
10370
|
+
...evt.replayed === true ? { replayed: true } : {},
|
|
10371
|
+
usage: {
|
|
10372
|
+
input_tokens: evt.input_tokens,
|
|
10373
|
+
output_tokens: evt.output_tokens,
|
|
10374
|
+
cache_creation_input_tokens: typeof evt.cache_creation_input_tokens === "number" ? evt.cache_creation_input_tokens : void 0,
|
|
10375
|
+
cache_read_input_tokens: typeof evt.cache_read_input_tokens === "number" ? evt.cache_read_input_tokens : void 0
|
|
10376
|
+
}
|
|
10377
|
+
});
|
|
10378
|
+
}
|
|
10379
|
+
break;
|
|
10380
|
+
}
|
|
10381
|
+
case "context_warning": {
|
|
10382
|
+
break;
|
|
10383
|
+
}
|
|
10384
|
+
case "thinking": {
|
|
10385
|
+
const text = typeof evt.text === "string" ? evt.text : "";
|
|
10386
|
+
if (text) {
|
|
10387
|
+
emittedToCaller = true;
|
|
10388
|
+
emittedChars += text.length;
|
|
10389
|
+
emittedAnythingAcrossAttempts = true;
|
|
10390
|
+
emittedCharsAcrossAttempts += text.length;
|
|
10391
|
+
onEvent({ type: "thinking", text });
|
|
10392
|
+
}
|
|
10393
|
+
break;
|
|
10394
|
+
}
|
|
10395
|
+
case "thinking_progress": {
|
|
10396
|
+
const tokens = typeof evt.tokens === "number" ? evt.tokens : 0;
|
|
10397
|
+
onEvent({ type: "thinking_progress", tokens });
|
|
10398
|
+
break;
|
|
10399
|
+
}
|
|
10400
|
+
case "thinking_delta": {
|
|
10401
|
+
const text = typeof evt.text === "string" ? evt.text : "";
|
|
10402
|
+
if (text) {
|
|
10403
|
+
emittedToCaller = true;
|
|
10404
|
+
emittedChars += text.length;
|
|
10405
|
+
emittedAnythingAcrossAttempts = true;
|
|
10406
|
+
emittedCharsAcrossAttempts += text.length;
|
|
10407
|
+
onEvent({ type: "thinking_delta", text });
|
|
10408
|
+
}
|
|
10409
|
+
break;
|
|
10410
|
+
}
|
|
10411
|
+
case "resumable": {
|
|
10412
|
+
serverResumable = true;
|
|
10413
|
+
break;
|
|
10414
|
+
}
|
|
10415
|
+
case "balance_status": {
|
|
10416
|
+
const balance = typeof evt.balance === "number" ? evt.balance : 0;
|
|
10417
|
+
const zero = !!evt.zero;
|
|
10418
|
+
onEvent({ type: "balance_status", balance, zero });
|
|
10419
|
+
break;
|
|
10420
|
+
}
|
|
10421
|
+
case "done": {
|
|
10422
|
+
onEvent({ type: "done" });
|
|
10430
10423
|
resolve3();
|
|
10431
|
-
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
|
|
10424
|
+
break;
|
|
10425
|
+
}
|
|
10426
|
+
case "error": {
|
|
10427
|
+
const message = typeof evt.message === "string" ? evt.message : typeof evt.error === "string" ? evt.error : "Unknown SSE error";
|
|
10428
|
+
const notResumable = evt.notResumable === true;
|
|
10429
|
+
if (haveCompleteMessage()) {
|
|
10430
|
+
clearInterval(heartbeatWatchdog);
|
|
10431
|
+
onEvent({ type: "error", message });
|
|
10432
|
+
resolve3();
|
|
10433
|
+
} else if (notResumable && (!emittedToCaller || allowRestartAfterRender)) {
|
|
10434
|
+
clearInterval(heartbeatWatchdog);
|
|
10435
|
+
stream.destroy?.();
|
|
10436
|
+
reject(Object.assign(tagTransient(new Error(message)), { forceRestart: true }));
|
|
10437
|
+
} else if (isRetryableStreamMsg(message) && (!emittedToCaller || allowRestartAfterRender)) {
|
|
10438
|
+
clearInterval(heartbeatWatchdog);
|
|
10439
|
+
stream.destroy?.();
|
|
10440
|
+
reject(tagTransient(new Error(message)));
|
|
10441
|
+
} else {
|
|
10442
|
+
clearInterval(heartbeatWatchdog);
|
|
10443
|
+
stream.destroy?.();
|
|
10444
|
+
onEvent({ type: "error", message });
|
|
10445
|
+
reject(new Error(message));
|
|
10446
|
+
}
|
|
10447
|
+
break;
|
|
10444
10448
|
}
|
|
10445
|
-
break;
|
|
10446
10449
|
}
|
|
10447
10450
|
}
|
|
10448
|
-
}
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
}
|
|
10464
|
-
resolve3();
|
|
10465
|
-
});
|
|
10466
|
-
stream.on("error", (err) => {
|
|
10467
|
-
clearInterval(heartbeatWatchdog);
|
|
10468
|
-
if (abortSignal?.aborted || controller.signal.aborted) {
|
|
10469
|
-
resolve3();
|
|
10470
|
-
return;
|
|
10471
|
-
}
|
|
10472
|
-
if (haveCompleteMessage()) {
|
|
10451
|
+
});
|
|
10452
|
+
stream.on("data", (chunk) => {
|
|
10453
|
+
lastDataAt = Date.now();
|
|
10454
|
+
if (abortSignal?.aborted) {
|
|
10455
|
+
stream.destroy?.();
|
|
10456
|
+
return;
|
|
10457
|
+
}
|
|
10458
|
+
parser.feed(chunk.toString("utf-8"));
|
|
10459
|
+
});
|
|
10460
|
+
stream.on("end", () => {
|
|
10461
|
+
clearInterval(heartbeatWatchdog);
|
|
10462
|
+
if (!sawModelEvent && !completedMessage) {
|
|
10463
|
+
reject(Object.assign(new Error("Connection closed before the model responded. Retrying\u2026"), { retryable: true }));
|
|
10464
|
+
return;
|
|
10465
|
+
}
|
|
10473
10466
|
resolve3();
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10467
|
+
});
|
|
10468
|
+
stream.on("error", (err) => {
|
|
10469
|
+
clearInterval(heartbeatWatchdog);
|
|
10470
|
+
if (abortSignal?.aborted || controller.signal.aborted) {
|
|
10471
|
+
resolve3();
|
|
10472
|
+
return;
|
|
10473
|
+
}
|
|
10474
|
+
if (haveCompleteMessage()) {
|
|
10475
|
+
resolve3();
|
|
10476
|
+
return;
|
|
10477
|
+
}
|
|
10478
|
+
reject(tagTransient(err));
|
|
10479
|
+
});
|
|
10477
10480
|
});
|
|
10478
|
-
}
|
|
10481
|
+
} finally {
|
|
10482
|
+
if (heartbeatWatchdog !== void 0)
|
|
10483
|
+
clearInterval(heartbeatWatchdog);
|
|
10484
|
+
}
|
|
10479
10485
|
if (completedMessage) {
|
|
10480
10486
|
return completedMessage;
|
|
10481
10487
|
}
|
|
@@ -15192,6 +15198,9 @@ var require_rules = __commonJS({
|
|
|
15192
15198
|
case "stock_photo":
|
|
15193
15199
|
vals.push(str(input.path));
|
|
15194
15200
|
break;
|
|
15201
|
+
case "task":
|
|
15202
|
+
vals.push(str(input.subagent_type));
|
|
15203
|
+
break;
|
|
15195
15204
|
default:
|
|
15196
15205
|
pushPath(str(input.path));
|
|
15197
15206
|
break;
|
|
@@ -15232,6 +15241,350 @@ var require_rules = __commonJS({
|
|
|
15232
15241
|
}
|
|
15233
15242
|
});
|
|
15234
15243
|
|
|
15244
|
+
// ../core/dist/agent/planMode.js
|
|
15245
|
+
var require_planMode = __commonJS({
|
|
15246
|
+
"../core/dist/agent/planMode.js"(exports) {
|
|
15247
|
+
"use strict";
|
|
15248
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15249
|
+
exports.PLAN_MODE_INSTRUCTIONS = void 0;
|
|
15250
|
+
exports.isReadOnlyCommand = isReadOnlyCommand;
|
|
15251
|
+
exports.checkPlanMode = checkPlanMode;
|
|
15252
|
+
var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
15253
|
+
"read_file",
|
|
15254
|
+
"search_files",
|
|
15255
|
+
"glob",
|
|
15256
|
+
"list_directory",
|
|
15257
|
+
"notebook_read",
|
|
15258
|
+
// Planning bookkeeping. todo_write persists only to the session's own todo
|
|
15259
|
+
// list, not the repo, so it stays available — a plan mode that cannot draft
|
|
15260
|
+
// a checklist is missing the point of plan mode.
|
|
15261
|
+
"todo_write",
|
|
15262
|
+
"todo_read",
|
|
15263
|
+
// Reading memory is fine; memory_write is NOT here on purpose. Memory is
|
|
15264
|
+
// durable state that survives the session, so writing it is a real mutation
|
|
15265
|
+
// even though no file in the repo changes.
|
|
15266
|
+
"memory_read",
|
|
15267
|
+
"use_skill",
|
|
15268
|
+
"fetch_url",
|
|
15269
|
+
"web_search",
|
|
15270
|
+
// Delegating research is ALLOWED, and this is safe rather than a hole:
|
|
15271
|
+
// runSubTask passes planMode down, so the sub-agent is under the same lock and
|
|
15272
|
+
// its own write tools are refused by this very function one level deeper.
|
|
15273
|
+
//
|
|
15274
|
+
// Worth allowing rather than blanket-refusing, because plan mode exists to
|
|
15275
|
+
// support deep research and the explorer agent is the cheapest way to do bulk
|
|
15276
|
+
// searching without flooding the main context. Blocking `task` would have made
|
|
15277
|
+
// the mode's headline use case worse, for no security gain.
|
|
15278
|
+
"task",
|
|
15279
|
+
// VS Code language server — all pure queries.
|
|
15280
|
+
"get_symbols",
|
|
15281
|
+
"get_workspace_symbols",
|
|
15282
|
+
"find_references",
|
|
15283
|
+
"go_to_definition",
|
|
15284
|
+
"get_hover",
|
|
15285
|
+
"get_diagnostics",
|
|
15286
|
+
// Reading output from an ALREADY-running background shell. Starting one is
|
|
15287
|
+
// gated with the rest of bash below; draining a buffer is not a new effect.
|
|
15288
|
+
"bash_output"
|
|
15289
|
+
]);
|
|
15290
|
+
var READ_ONLY_BASH = /* @__PURE__ */ new Set([
|
|
15291
|
+
"ls",
|
|
15292
|
+
"cat",
|
|
15293
|
+
"head",
|
|
15294
|
+
"tail",
|
|
15295
|
+
"wc",
|
|
15296
|
+
"file",
|
|
15297
|
+
"stat",
|
|
15298
|
+
"du",
|
|
15299
|
+
"df",
|
|
15300
|
+
"pwd",
|
|
15301
|
+
"which",
|
|
15302
|
+
"type",
|
|
15303
|
+
"echo",
|
|
15304
|
+
"printf",
|
|
15305
|
+
"basename",
|
|
15306
|
+
"dirname",
|
|
15307
|
+
"realpath",
|
|
15308
|
+
"readlink",
|
|
15309
|
+
"grep",
|
|
15310
|
+
"egrep",
|
|
15311
|
+
"fgrep",
|
|
15312
|
+
"rg",
|
|
15313
|
+
"ag",
|
|
15314
|
+
"ack",
|
|
15315
|
+
"find",
|
|
15316
|
+
"fd",
|
|
15317
|
+
"locate",
|
|
15318
|
+
"diff",
|
|
15319
|
+
"comm",
|
|
15320
|
+
"cmp",
|
|
15321
|
+
"sort",
|
|
15322
|
+
"uniq",
|
|
15323
|
+
"cut",
|
|
15324
|
+
"tr",
|
|
15325
|
+
"column",
|
|
15326
|
+
"jq",
|
|
15327
|
+
"yq",
|
|
15328
|
+
"date",
|
|
15329
|
+
"whoami",
|
|
15330
|
+
"hostname",
|
|
15331
|
+
"uname",
|
|
15332
|
+
"env",
|
|
15333
|
+
"printenv",
|
|
15334
|
+
"id",
|
|
15335
|
+
"groups",
|
|
15336
|
+
"ps",
|
|
15337
|
+
"top",
|
|
15338
|
+
"uptime",
|
|
15339
|
+
"free",
|
|
15340
|
+
"node",
|
|
15341
|
+
"python",
|
|
15342
|
+
"python3",
|
|
15343
|
+
"tree",
|
|
15344
|
+
"less",
|
|
15345
|
+
"more",
|
|
15346
|
+
"nl",
|
|
15347
|
+
"tac",
|
|
15348
|
+
"strings",
|
|
15349
|
+
"md5sum",
|
|
15350
|
+
"sha256sum",
|
|
15351
|
+
"true",
|
|
15352
|
+
"false",
|
|
15353
|
+
"test",
|
|
15354
|
+
"sleep",
|
|
15355
|
+
"seq",
|
|
15356
|
+
"expr"
|
|
15357
|
+
]);
|
|
15358
|
+
var READ_ONLY_SUBCOMMANDS = {
|
|
15359
|
+
git: /* @__PURE__ */ new Set([
|
|
15360
|
+
"log",
|
|
15361
|
+
"show",
|
|
15362
|
+
"diff",
|
|
15363
|
+
"status",
|
|
15364
|
+
"blame",
|
|
15365
|
+
"branch",
|
|
15366
|
+
"tag",
|
|
15367
|
+
"remote",
|
|
15368
|
+
"describe",
|
|
15369
|
+
"rev-parse",
|
|
15370
|
+
"rev-list",
|
|
15371
|
+
"ls-files",
|
|
15372
|
+
"ls-tree",
|
|
15373
|
+
"ls-remote",
|
|
15374
|
+
"cat-file",
|
|
15375
|
+
"shortlog",
|
|
15376
|
+
"reflog",
|
|
15377
|
+
"whatchanged",
|
|
15378
|
+
"grep",
|
|
15379
|
+
"config",
|
|
15380
|
+
"check-ignore",
|
|
15381
|
+
"merge-base",
|
|
15382
|
+
"name-rev",
|
|
15383
|
+
"count-objects",
|
|
15384
|
+
"verify-commit"
|
|
15385
|
+
]),
|
|
15386
|
+
// Package managers: only their query verbs.
|
|
15387
|
+
npm: /* @__PURE__ */ new Set(["ls", "list", "view", "info", "outdated", "why", "config", "ping", "search"]),
|
|
15388
|
+
pnpm: /* @__PURE__ */ new Set(["ls", "list", "view", "info", "outdated", "why", "config"]),
|
|
15389
|
+
yarn: /* @__PURE__ */ new Set(["list", "info", "why", "config"]),
|
|
15390
|
+
cargo: /* @__PURE__ */ new Set(["tree", "metadata", "search"]),
|
|
15391
|
+
go: /* @__PURE__ */ new Set(["list", "version", "env", "vet", "doc"]),
|
|
15392
|
+
docker: /* @__PURE__ */ new Set(["ps", "images", "logs", "inspect", "version", "info", "top", "port", "diff", "stats"]),
|
|
15393
|
+
kubectl: /* @__PURE__ */ new Set(["get", "describe", "logs", "explain", "top", "version", "api-resources", "api-versions"]),
|
|
15394
|
+
// gh: read verbs only. `gh run rerun`, `gh pr merge` etc. are excluded, and
|
|
15395
|
+
// the nested-verb check in isReadOnlyCommand handles `gh pr view` vs `gh pr merge`.
|
|
15396
|
+
gh: /* @__PURE__ */ new Set(["browse"])
|
|
15397
|
+
};
|
|
15398
|
+
var READ_ONLY_NESTED = {
|
|
15399
|
+
gh: /* @__PURE__ */ new Set(["view", "list", "status", "diff", "checks"])
|
|
15400
|
+
};
|
|
15401
|
+
var SHELL_CONTROL = /[\n\r;&|`]|\$\(|>>|>|<\(/;
|
|
15402
|
+
function firstWords(command) {
|
|
15403
|
+
return command.trim().split(/\s+/).filter(Boolean);
|
|
15404
|
+
}
|
|
15405
|
+
function isReadOnlyCommand(command) {
|
|
15406
|
+
const cmd = command.trim();
|
|
15407
|
+
if (!cmd)
|
|
15408
|
+
return false;
|
|
15409
|
+
if (SHELL_CONTROL.test(cmd))
|
|
15410
|
+
return false;
|
|
15411
|
+
const words = firstWords(cmd);
|
|
15412
|
+
if (!words.length)
|
|
15413
|
+
return false;
|
|
15414
|
+
let i2 = 0;
|
|
15415
|
+
while (i2 < words.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[i2]))
|
|
15416
|
+
i2++;
|
|
15417
|
+
if (i2 >= words.length)
|
|
15418
|
+
return false;
|
|
15419
|
+
let verb = words[i2];
|
|
15420
|
+
const slash = verb.lastIndexOf("/");
|
|
15421
|
+
if (slash >= 0)
|
|
15422
|
+
verb = verb.slice(slash + 1);
|
|
15423
|
+
if (verb === "sudo" || verb === "doas" || verb === "su")
|
|
15424
|
+
return false;
|
|
15425
|
+
const rest = words.slice(i2 + 1).filter((w) => !w.startsWith("-"));
|
|
15426
|
+
const subs = READ_ONLY_SUBCOMMANDS[verb];
|
|
15427
|
+
if (subs) {
|
|
15428
|
+
const sub = rest[0];
|
|
15429
|
+
if (!sub) {
|
|
15430
|
+
return true;
|
|
15431
|
+
}
|
|
15432
|
+
if (subs.has(sub)) {
|
|
15433
|
+
if (verb === "git" && sub === "config") {
|
|
15434
|
+
const args = words.slice(i2 + 2).filter((w) => !w.startsWith("-"));
|
|
15435
|
+
return args.length <= 1;
|
|
15436
|
+
}
|
|
15437
|
+
return true;
|
|
15438
|
+
}
|
|
15439
|
+
const nested = READ_ONLY_NESTED[verb];
|
|
15440
|
+
if (nested && rest[1] && nested.has(rest[1]))
|
|
15441
|
+
return true;
|
|
15442
|
+
return false;
|
|
15443
|
+
}
|
|
15444
|
+
if (!READ_ONLY_TOOLS.has(verb) && !READ_ONLY_BASH.has(verb))
|
|
15445
|
+
return false;
|
|
15446
|
+
if (verb === "node" || verb === "python" || verb === "python3") {
|
|
15447
|
+
return words.slice(i2 + 1).every((w) => w === "--version" || w === "-V" || w === "-v");
|
|
15448
|
+
}
|
|
15449
|
+
if (verb === "find" && words.some((w) => w === "-exec" || w === "-execdir" || w === "-delete")) {
|
|
15450
|
+
return false;
|
|
15451
|
+
}
|
|
15452
|
+
return true;
|
|
15453
|
+
}
|
|
15454
|
+
function checkPlanMode(tool, input) {
|
|
15455
|
+
if (tool === "bash") {
|
|
15456
|
+
const command = typeof input.command === "string" ? input.command : "";
|
|
15457
|
+
if (input.run_in_background === true) {
|
|
15458
|
+
return {
|
|
15459
|
+
reason: "bash-not-read-only",
|
|
15460
|
+
message: "Plan mode is ON: background processes cannot be started. Finish researching and propose your plan; the user will leave plan mode if they want it carried out."
|
|
15461
|
+
};
|
|
15462
|
+
}
|
|
15463
|
+
if (isReadOnlyCommand(command))
|
|
15464
|
+
return null;
|
|
15465
|
+
return {
|
|
15466
|
+
reason: "bash-not-read-only",
|
|
15467
|
+
message: `Plan mode is ON, so \`bash\` is limited to provably read-only commands and this one was not recognised as such (chained commands, redirection and command substitution are always refused). Do NOT ask the user to approve it and do NOT try a variation to get around this \u2014 it is a session-wide lock, not a per-call prompt. Use read-only inspection instead, and put the command in your plan as a step to run once plan mode is off.`
|
|
15468
|
+
};
|
|
15469
|
+
}
|
|
15470
|
+
if (READ_ONLY_TOOLS.has(tool))
|
|
15471
|
+
return null;
|
|
15472
|
+
return {
|
|
15473
|
+
reason: "mutating-tool",
|
|
15474
|
+
message: `Plan mode is ON, so \`${tool}\` is unavailable \u2014 this session may not modify anything yet. Do NOT ask for approval: no answer the user gives at this prompt can unlock it, because it is a session-wide lock they control directly. Research with read-only tools and finish by proposing a concrete plan (files to change, in order, and how to verify). The user will exit plan mode to let you carry it out.`
|
|
15475
|
+
};
|
|
15476
|
+
}
|
|
15477
|
+
exports.PLAN_MODE_INSTRUCTIONS = [
|
|
15478
|
+
"# PLAN MODE IS ACTIVE",
|
|
15479
|
+
"",
|
|
15480
|
+
"This session is READ-ONLY. You cannot edit files, write files, run mutating commands, or start",
|
|
15481
|
+
"background processes. This is enforced outside your control \u2014 it is not a permission prompt, and",
|
|
15482
|
+
"the user cannot approve an exception from inside this turn.",
|
|
15483
|
+
"",
|
|
15484
|
+
"Therefore:",
|
|
15485
|
+
'- NEVER ask "shall I go ahead and make this change?" \u2014 you cannot, whatever the answer.',
|
|
15486
|
+
'- NEVER attempt a mutating tool "just to see" \u2014 it will be refused and wastes the turn.',
|
|
15487
|
+
"- NEVER try to reach a blocked command another way (a different flag, a script, an interpreter).",
|
|
15488
|
+
"",
|
|
15489
|
+
"Your job is to research thoroughly, then deliver a plan:",
|
|
15490
|
+
"1. What you found \u2014 the specific files, functions and line numbers that matter.",
|
|
15491
|
+
"2. What you propose to change \u2014 file by file, in dependency order.",
|
|
15492
|
+
"3. Risks and unknowns \u2014 what could break, what you could not verify, what you assumed.",
|
|
15493
|
+
"4. How it will be verified \u2014 the exact build/test/lint command that proves it works.",
|
|
15494
|
+
"",
|
|
15495
|
+
'Be concrete. "Update the auth logic" is not a plan; "add a `refreshToken` field to AuthConfig in',
|
|
15496
|
+
'auth/index.ts:42, then thread it through client.ts:118" is. The user will review the plan and',
|
|
15497
|
+
"leave plan mode to have it carried out."
|
|
15498
|
+
].join("\n");
|
|
15499
|
+
}
|
|
15500
|
+
});
|
|
15501
|
+
|
|
15502
|
+
// ../core/dist/agent/agentRegistry.js
|
|
15503
|
+
var require_agentRegistry = __commonJS({
|
|
15504
|
+
"../core/dist/agent/agentRegistry.js"(exports) {
|
|
15505
|
+
"use strict";
|
|
15506
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15507
|
+
exports._limits = void 0;
|
|
15508
|
+
exports.rememberAgent = rememberAgent;
|
|
15509
|
+
exports.updateAgent = updateAgent;
|
|
15510
|
+
exports.getAgent = getAgent;
|
|
15511
|
+
exports.listAgents = listAgents;
|
|
15512
|
+
exports._resetAgentRegistry = _resetAgentRegistry;
|
|
15513
|
+
var MAX_AGENTS = 20;
|
|
15514
|
+
var MAX_TOTAL_BYTES = 8 * 1024 * 1024;
|
|
15515
|
+
var _agents = /* @__PURE__ */ new Map();
|
|
15516
|
+
var _counter = 0;
|
|
15517
|
+
var _seq = 0;
|
|
15518
|
+
function totalBytes() {
|
|
15519
|
+
let n = 0;
|
|
15520
|
+
for (const a of _agents.values())
|
|
15521
|
+
n += a.bytes;
|
|
15522
|
+
return n;
|
|
15523
|
+
}
|
|
15524
|
+
function evictUntilWithinLimits() {
|
|
15525
|
+
while (_agents.size > MAX_AGENTS) {
|
|
15526
|
+
const oldest = _agents.keys().next().value;
|
|
15527
|
+
if (oldest === void 0)
|
|
15528
|
+
break;
|
|
15529
|
+
_agents.delete(oldest);
|
|
15530
|
+
}
|
|
15531
|
+
while (totalBytes() > MAX_TOTAL_BYTES && _agents.size > 1) {
|
|
15532
|
+
const oldest = _agents.keys().next().value;
|
|
15533
|
+
if (oldest === void 0)
|
|
15534
|
+
break;
|
|
15535
|
+
_agents.delete(oldest);
|
|
15536
|
+
}
|
|
15537
|
+
}
|
|
15538
|
+
function sizeOf(messages) {
|
|
15539
|
+
try {
|
|
15540
|
+
return JSON.stringify(messages).length;
|
|
15541
|
+
} catch {
|
|
15542
|
+
return MAX_TOTAL_BYTES;
|
|
15543
|
+
}
|
|
15544
|
+
}
|
|
15545
|
+
function rememberAgent(agentName, description, messages) {
|
|
15546
|
+
const id = `agent_${++_counter}`;
|
|
15547
|
+
const now = Date.now();
|
|
15548
|
+
_agents.set(id, {
|
|
15549
|
+
id,
|
|
15550
|
+
agentName,
|
|
15551
|
+
description,
|
|
15552
|
+
messages,
|
|
15553
|
+
createdAt: now,
|
|
15554
|
+
updatedAt: now,
|
|
15555
|
+
seq: ++_seq,
|
|
15556
|
+
bytes: sizeOf(messages)
|
|
15557
|
+
});
|
|
15558
|
+
evictUntilWithinLimits();
|
|
15559
|
+
return id;
|
|
15560
|
+
}
|
|
15561
|
+
function updateAgent(id, messages) {
|
|
15562
|
+
const existing = _agents.get(id);
|
|
15563
|
+
if (!existing)
|
|
15564
|
+
return;
|
|
15565
|
+
existing.messages = messages;
|
|
15566
|
+
existing.updatedAt = Date.now();
|
|
15567
|
+
existing.seq = ++_seq;
|
|
15568
|
+
existing.bytes = sizeOf(messages);
|
|
15569
|
+
_agents.delete(id);
|
|
15570
|
+
_agents.set(id, existing);
|
|
15571
|
+
evictUntilWithinLimits();
|
|
15572
|
+
}
|
|
15573
|
+
function getAgent(id) {
|
|
15574
|
+
return _agents.get(id);
|
|
15575
|
+
}
|
|
15576
|
+
function listAgents() {
|
|
15577
|
+
return [..._agents.values()].sort((a, b) => b.seq - a.seq);
|
|
15578
|
+
}
|
|
15579
|
+
function _resetAgentRegistry() {
|
|
15580
|
+
_agents.clear();
|
|
15581
|
+
_counter = 0;
|
|
15582
|
+
_seq = 0;
|
|
15583
|
+
}
|
|
15584
|
+
exports._limits = { MAX_AGENTS, MAX_TOTAL_BYTES };
|
|
15585
|
+
}
|
|
15586
|
+
});
|
|
15587
|
+
|
|
15235
15588
|
// ../core/dist/agent/flaky.js
|
|
15236
15589
|
var require_flaky = __commonJS({
|
|
15237
15590
|
"../core/dist/agent/flaky.js"(exports) {
|
|
@@ -15410,7 +15763,8 @@ var require_loop = __commonJS({
|
|
|
15410
15763
|
};
|
|
15411
15764
|
}();
|
|
15412
15765
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15413
|
-
exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = exports.ToolNotAllowedError = void 0;
|
|
15766
|
+
exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = exports.ToolNotAllowedError = exports._stallLimits = void 0;
|
|
15767
|
+
exports.errorRoundSignature = errorRoundSignature;
|
|
15414
15768
|
exports.resolveMaxIterations = resolveMaxIterations;
|
|
15415
15769
|
exports.createLimiter = createLimiter;
|
|
15416
15770
|
exports.extractSubTaskText = extractSubTaskText;
|
|
@@ -15436,6 +15790,8 @@ var require_loop = __commonJS({
|
|
|
15436
15790
|
var agentTypes_1 = require_agentTypes();
|
|
15437
15791
|
var skills_1 = require_skills();
|
|
15438
15792
|
var rules_1 = require_rules();
|
|
15793
|
+
var planMode_1 = require_planMode();
|
|
15794
|
+
var agentRegistry_1 = require_agentRegistry();
|
|
15439
15795
|
var sandbox_1 = require_sandbox();
|
|
15440
15796
|
var index_1 = require_plugins();
|
|
15441
15797
|
var testIntegrity_1 = require_testIntegrity();
|
|
@@ -15541,6 +15897,11 @@ var require_loop = __commonJS({
|
|
|
15541
15897
|
var MAX_ITERATIONS_CEILING = 2e3;
|
|
15542
15898
|
var HARD_ITERATIONS_CAP = 1e5;
|
|
15543
15899
|
var STALL_LIMIT = 8;
|
|
15900
|
+
var REPEAT_STALL_LIMIT = 12;
|
|
15901
|
+
function errorRoundSignature(errored) {
|
|
15902
|
+
return errored.map(({ name, error }) => `${name}:${String(error).slice(0, 200)}`).sort().join("|");
|
|
15903
|
+
}
|
|
15904
|
+
exports._stallLimits = { STALL_LIMIT, REPEAT_STALL_LIMIT };
|
|
15544
15905
|
function resolveMaxIterations(optionValue, settingsRaw) {
|
|
15545
15906
|
const fromEnv = Number(process.env.NEXRALL_MAX_ITERATIONS);
|
|
15546
15907
|
const fromSettings = Number(settingsRaw.maxIterations);
|
|
@@ -15756,7 +16117,22 @@ ${tail}`;
|
|
|
15756
16117
|
if (depth >= MAX_TASK_DEPTH) {
|
|
15757
16118
|
return { error: "Sub-agents cannot spawn further sub-agents. Do this work directly, or report back so the main agent can delegate it." };
|
|
15758
16119
|
}
|
|
15759
|
-
const
|
|
16120
|
+
const resumeId = typeof input.resume_agent_id === "string" ? input.resume_agent_id.trim() : "";
|
|
16121
|
+
const resumed = resumeId ? (0, agentRegistry_1.getAgent)(resumeId) : void 0;
|
|
16122
|
+
if (resumeId && !resumed) {
|
|
16123
|
+
return {
|
|
16124
|
+
error: `No resumable sub-agent with id "${resumeId}". Ids live only for the current session and the oldest are dropped when too many accumulate, so this one has expired or never existed. Start a fresh sub-task with a self-contained prompt instead.`
|
|
16125
|
+
};
|
|
16126
|
+
}
|
|
16127
|
+
const requestedType = resumed ? resumed.agentName ?? "" : typeof input.subagent_type === "string" ? input.subagent_type : "";
|
|
16128
|
+
if (requestedType) {
|
|
16129
|
+
const decision = (0, rules_1.evaluatePermission)((0, rules_1.loadSettings)(options.workDir).permissions, "task", { subagent_type: requestedType }, options.workDir);
|
|
16130
|
+
if (decision === "deny") {
|
|
16131
|
+
return {
|
|
16132
|
+
error: `The sub-agent "${requestedType}" is disabled by a permission rule in this project (permissions.deny in settings.json). This is a deliberate policy choice, not a missing file \u2014 do not create it and do not retry. Do the work yourself, or use a different sub-agent.`
|
|
16133
|
+
};
|
|
16134
|
+
}
|
|
16135
|
+
}
|
|
15760
16136
|
let agent = (0, agentTypes_1.findAgentType)(agentTypes, requestedType);
|
|
15761
16137
|
let knownTypes = agentTypes;
|
|
15762
16138
|
if (requestedType && !agent) {
|
|
@@ -15786,9 +16162,7 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
15786
16162
|
}
|
|
15787
16163
|
return options.requestPermission(req);
|
|
15788
16164
|
};
|
|
15789
|
-
const subMessages = [
|
|
15790
|
-
{ role: "user", content: [{ type: "text", text: prompt2 }] }
|
|
15791
|
-
];
|
|
16165
|
+
const subMessages = resumed ? [...resumed.messages, { role: "user", content: [{ type: "text", text: prompt2 }] }] : [{ role: "user", content: [{ type: "text", text: prompt2 }] }];
|
|
15792
16166
|
const subAbort = { aborted: false };
|
|
15793
16167
|
const timer = setTimeout(() => {
|
|
15794
16168
|
subAbort.aborted = true;
|
|
@@ -15806,6 +16180,9 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
15806
16180
|
editorContext: null,
|
|
15807
16181
|
// fresh isolated context for sub-agent
|
|
15808
16182
|
model: agent?.model ?? options.model,
|
|
16183
|
+
// Plan mode is inherited, never relaxed. If the main agent could spawn a
|
|
16184
|
+
// sub-agent that writes, the lock would be one `task` call from useless.
|
|
16185
|
+
planMode: options.planMode,
|
|
15809
16186
|
nexrallMd: subNexrallMd,
|
|
15810
16187
|
abortSignal: subAbort,
|
|
15811
16188
|
requestPermission: gatedPermission,
|
|
@@ -15854,7 +16231,13 @@ ${partial}` : "",
|
|
|
15854
16231
|
return { error: sections.join("\n\n") };
|
|
15855
16232
|
}
|
|
15856
16233
|
const text = capSubTaskText(extractSubTaskText(result, true));
|
|
15857
|
-
|
|
16234
|
+
const agentId = resumed ? ((0, agentRegistry_1.updateAgent)(resumed.id, result), resumed.id) : (0, agentRegistry_1.rememberAgent)(agent?.name ?? null, typeof input.description === "string" && input.description.trim() || prompt2.slice(0, 80), result);
|
|
16235
|
+
const body = text || "(sub-task completed with no text output)";
|
|
16236
|
+
return {
|
|
16237
|
+
output: `${body}
|
|
16238
|
+
|
|
16239
|
+
[resumable: this sub-agent is "${agentId}". To ask IT a follow-up \u2014 keeping everything it already read and concluded \u2014 call task again with resume_agent_id="` + agentId + '" instead of writing a new prompt from scratch.]'
|
|
16240
|
+
};
|
|
15858
16241
|
} catch (err) {
|
|
15859
16242
|
const salvaged = (0, types_1.salvageHistory)(err);
|
|
15860
16243
|
if (salvaged) {
|
|
@@ -15895,6 +16278,8 @@ ${partial}` : "",
|
|
|
15895
16278
|
}
|
|
15896
16279
|
var PRUNE_MIN_RECLAIM_BYTES = 256 * 1024;
|
|
15897
16280
|
var COMPACT_KEEP_MIN = 6;
|
|
16281
|
+
var COMPACT_MIN_RECLAIM_BYTES = 32 * 1024;
|
|
16282
|
+
var COMPACT_MAX_FAILURES = 3;
|
|
15898
16283
|
var MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
15899
16284
|
function estimateBodyBytes2(messages) {
|
|
15900
16285
|
try {
|
|
@@ -15976,7 +16361,7 @@ ${tail}`;
|
|
|
15976
16361
|
var LEDGER_MAX_FILES = 60;
|
|
15977
16362
|
var LEDGER_MAX_NOTES = 20;
|
|
15978
16363
|
function createLedger() {
|
|
15979
|
-
return { filesTouched: /* @__PURE__ */ new Map(), verifications: [], testIntegrity: [], epoch: 0 };
|
|
16364
|
+
return { filesTouched: /* @__PURE__ */ new Map(), filesTouchedTotal: 0, verifications: [], testIntegrity: [], testIntegrityTotal: 0, epoch: 0 };
|
|
15980
16365
|
}
|
|
15981
16366
|
function ledgerRecord(ledger, toolName, input, ok, output, exitCode) {
|
|
15982
16367
|
if (exports.WRITE_TOOL_NAMES.has(toolName)) {
|
|
@@ -15986,7 +16371,18 @@ ${tail}`;
|
|
|
15986
16371
|
const p = typeof input?.path === "string" ? input.path : void 0;
|
|
15987
16372
|
if (p) {
|
|
15988
16373
|
const prev = ledger.filesTouched.get(p);
|
|
16374
|
+
if (!prev)
|
|
16375
|
+
ledger.filesTouchedTotal++;
|
|
16376
|
+
ledger.filesTouched.delete(p);
|
|
15989
16377
|
ledger.filesTouched.set(p, { tool: toolName, edits: (prev?.edits ?? 0) + 1 });
|
|
16378
|
+
if (ledger.filesTouched.size > LEDGER_MAX_FILES * 4) {
|
|
16379
|
+
for (const key of ledger.filesTouched.keys()) {
|
|
16380
|
+
if (ledger.filesTouched.size <= LEDGER_MAX_FILES * 2)
|
|
16381
|
+
break;
|
|
16382
|
+
if (key !== p)
|
|
16383
|
+
ledger.filesTouched.delete(key);
|
|
16384
|
+
}
|
|
16385
|
+
}
|
|
15990
16386
|
}
|
|
15991
16387
|
const reasons = [];
|
|
15992
16388
|
const markerReasons = toolName === "write_file" ? (0, testIntegrity_1.decodeTestIntegrityMarker)(output) : [];
|
|
@@ -16000,6 +16396,7 @@ ${tail}`;
|
|
|
16000
16396
|
if (reasons.length && p) {
|
|
16001
16397
|
for (const reason of reasons) {
|
|
16002
16398
|
ledger.testIntegrity.push({ path: p, reason });
|
|
16399
|
+
ledger.testIntegrityTotal++;
|
|
16003
16400
|
}
|
|
16004
16401
|
if (ledger.testIntegrity.length > LEDGER_MAX_NOTES * 2) {
|
|
16005
16402
|
ledger.testIntegrity.splice(0, ledger.testIntegrity.length - LEDGER_MAX_NOTES);
|
|
@@ -16020,8 +16417,8 @@ ${tail}`;
|
|
|
16020
16417
|
const lines = [];
|
|
16021
16418
|
if (ledger.filesTouched.size) {
|
|
16022
16419
|
const files = [...ledger.filesTouched.entries()];
|
|
16023
|
-
const shown = files.slice(
|
|
16024
|
-
lines.push(`FILES CHANGED THIS SESSION (${ledger.filesTouched.size}):`);
|
|
16420
|
+
const shown = files.slice(-LEDGER_MAX_FILES);
|
|
16421
|
+
lines.push(`FILES CHANGED THIS SESSION (${ledger.filesTouchedTotal || ledger.filesTouched.size}):`);
|
|
16025
16422
|
for (const [p, meta] of shown) {
|
|
16026
16423
|
lines.push(` \u2022 ${p} (${meta.tool}${meta.edits > 1 ? ` \xD7${meta.edits}` : ""})`);
|
|
16027
16424
|
}
|
|
@@ -16236,10 +16633,15 @@ Continue the work from here.` }] });
|
|
|
16236
16633
|
const hooks = loadHooks(options.workDir);
|
|
16237
16634
|
const depth = options._depth ?? 0;
|
|
16238
16635
|
const agentScope = options._agentScope ?? "root";
|
|
16239
|
-
const
|
|
16636
|
+
const settings = (0, rules_1.loadSettings)(options.workDir);
|
|
16637
|
+
const agentTypes = (0, agentTypes_1.loadAgentTypes)(options.workDir).filter((t2) => (0, rules_1.evaluatePermission)(settings.permissions, "task", { subagent_type: t2.name }, options.workDir) !== "deny");
|
|
16240
16638
|
const agentsCatalogue = depth === 0 ? (0, agentTypes_1.summariseAgents)(agentTypes) : "";
|
|
16241
16639
|
const skillsCatalogue = (0, skills_1.summariseSkills)((0, skills_1.loadSkills)(options.workDir));
|
|
16242
|
-
const
|
|
16640
|
+
const planAwareNexrallMd = options.planMode ? planMode_1.PLAN_MODE_INSTRUCTIONS + (options.nexrallMd ? `
|
|
16641
|
+
|
|
16642
|
+
---
|
|
16643
|
+
|
|
16644
|
+
${options.nexrallMd}` : "") : options.nexrallMd;
|
|
16243
16645
|
const sandboxCfg = (0, sandbox_1.parseSandboxConfig)(settings.raw.sandbox) ?? void 0;
|
|
16244
16646
|
const maxIterations = resolveMaxIterations(options.maxIterations, settings.raw);
|
|
16245
16647
|
const autoContinue = resolveAutoContinue(options.autoContinue, settings.raw);
|
|
@@ -16253,6 +16655,9 @@ Continue the work from here.` }] });
|
|
|
16253
16655
|
let completedRounds = 0;
|
|
16254
16656
|
let stalledOut = false;
|
|
16255
16657
|
let consecutiveErrorRounds = 0;
|
|
16658
|
+
let repeatedErrorRounds = 0;
|
|
16659
|
+
let lastErrorSignature = "";
|
|
16660
|
+
let stalledRepeatError = null;
|
|
16256
16661
|
let budget = maxIterations;
|
|
16257
16662
|
let iteration = 0;
|
|
16258
16663
|
let filesMutatedSinceVerify = false;
|
|
@@ -16262,6 +16667,8 @@ Continue the work from here.` }] });
|
|
|
16262
16667
|
const flakyNudgedCmds = /* @__PURE__ */ new Set();
|
|
16263
16668
|
let claimEvidenceNudged = false;
|
|
16264
16669
|
const ledger = createLedger();
|
|
16670
|
+
let compactFailures = 0;
|
|
16671
|
+
let compactDisabled = false;
|
|
16265
16672
|
try {
|
|
16266
16673
|
for (; iteration < budget; iteration++) {
|
|
16267
16674
|
if (options.abortSignal?.aborted)
|
|
@@ -16278,14 +16685,25 @@ Continue the work from here.` }] });
|
|
|
16278
16685
|
(options.onNotice ?? options.onText)(`\u267B\uFE0F Trimmed ~${(reclaimed / (1024 * 1024)).toFixed(1)}MB of already-processed tool output to keep this chat cheap to continue.`);
|
|
16279
16686
|
}
|
|
16280
16687
|
}
|
|
16281
|
-
if (autoCompact && !compacting && (tokenPressure || bytePressure) && messages.length > COMPACT_KEEP_MIN + 2) {
|
|
16688
|
+
if (autoCompact && !compactDisabled && !compacting && (tokenPressure || bytePressure) && messages.length > COMPACT_KEEP_MIN + 2) {
|
|
16282
16689
|
compacting = true;
|
|
16283
16690
|
try {
|
|
16691
|
+
const bytesBefore = estimateBodyBytes2(messages);
|
|
16284
16692
|
const did = await autoCompactMessages(messages, options, ledger);
|
|
16693
|
+
const bytesAfter = did ? estimateBodyBytes2(messages) : bytesBefore;
|
|
16694
|
+
const reclaimed = bytesBefore - bytesAfter;
|
|
16285
16695
|
if (did) {
|
|
16286
16696
|
lastPromptTokens = 0;
|
|
16287
16697
|
const reason = bytePressure ? `body ~${(bodyBytes / (1024 * 1024)).toFixed(1)}MB` : "context window";
|
|
16288
16698
|
(options.onNotice ?? options.onText)(`\u267B\uFE0F Auto-compacted earlier conversation to stay within the ${reason}.`);
|
|
16699
|
+
bodyBytes = bytesAfter;
|
|
16700
|
+
bytePressure = bodyBytes > MAX_BODY_BYTES;
|
|
16701
|
+
}
|
|
16702
|
+
if (did && reclaimed >= COMPACT_MIN_RECLAIM_BYTES) {
|
|
16703
|
+
compactFailures = 0;
|
|
16704
|
+
} else if (++compactFailures >= COMPACT_MAX_FAILURES) {
|
|
16705
|
+
compactDisabled = true;
|
|
16706
|
+
(options.onNotice ?? options.onText)(`\u26A0\uFE0F Auto-compaction isn't reducing this conversation any further, so it's been switched off for the rest of this run to avoid repeated summarising. If the context fills up, start a fresh chat or run /compact manually.`);
|
|
16289
16707
|
}
|
|
16290
16708
|
} finally {
|
|
16291
16709
|
compacting = false;
|
|
@@ -16347,7 +16765,7 @@ Continue the work from here.` }] });
|
|
|
16347
16765
|
effort: options.effort,
|
|
16348
16766
|
env: options.env,
|
|
16349
16767
|
editorContext: options.editorContext,
|
|
16350
|
-
nexrallMd:
|
|
16768
|
+
nexrallMd: planAwareNexrallMd,
|
|
16351
16769
|
clientType: options.clientType,
|
|
16352
16770
|
abortSignal: options.abortSignal,
|
|
16353
16771
|
extraTools: options.mcpManager?.getAnthropicTools(),
|
|
@@ -16400,9 +16818,10 @@ Continue the work from here.` }] });
|
|
|
16400
16818
|
messages.push({ role: "user", content: [{ type: "text", text }] });
|
|
16401
16819
|
continue;
|
|
16402
16820
|
}
|
|
16403
|
-
if (ledger.
|
|
16404
|
-
const
|
|
16405
|
-
|
|
16821
|
+
if (ledger.testIntegrityTotal > testIntegrityNudgedCount) {
|
|
16822
|
+
const newCount = Math.min(ledger.testIntegrityTotal - testIntegrityNudgedCount, ledger.testIntegrity.length);
|
|
16823
|
+
const fresh = ledger.testIntegrity.slice(ledger.testIntegrity.length - newCount);
|
|
16824
|
+
testIntegrityNudgedCount = ledger.testIntegrityTotal;
|
|
16406
16825
|
const bullet = fresh.map((t2) => ` \u2022 ${t2.path}: ${t2.reason}`).join("\n");
|
|
16407
16826
|
messages.push({
|
|
16408
16827
|
role: "user",
|
|
@@ -16467,6 +16886,14 @@ Continue the work from here.` }] });
|
|
|
16467
16886
|
options.onToolResult(name, result);
|
|
16468
16887
|
return { block: { ...block, id }, result };
|
|
16469
16888
|
}
|
|
16889
|
+
if (options.planMode) {
|
|
16890
|
+
const refusal = (0, planMode_1.checkPlanMode)(name, input);
|
|
16891
|
+
if (refusal) {
|
|
16892
|
+
result = { error: refusal.message };
|
|
16893
|
+
options.onToolResult(name, result);
|
|
16894
|
+
return { block: { ...block, id }, result };
|
|
16895
|
+
}
|
|
16896
|
+
}
|
|
16470
16897
|
const description = humanDescription(name, input);
|
|
16471
16898
|
let permitted;
|
|
16472
16899
|
let deniedReason = null;
|
|
@@ -16575,12 +17002,25 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
|
|
|
16575
17002
|
messages.push(toolResultMessage);
|
|
16576
17003
|
completedRounds++;
|
|
16577
17004
|
options.onProgress?.(messages);
|
|
16578
|
-
const
|
|
17005
|
+
const errored = toolResults.filter(({ result }) => result.error !== void 0);
|
|
17006
|
+
const allErrored = toolResults.length > 0 && errored.length === toolResults.length;
|
|
16579
17007
|
consecutiveErrorRounds = allErrored ? consecutiveErrorRounds + 1 : 0;
|
|
16580
17008
|
if (consecutiveErrorRounds >= STALL_LIMIT) {
|
|
16581
17009
|
stalledOut = true;
|
|
16582
17010
|
break;
|
|
16583
17011
|
}
|
|
17012
|
+
const errSignature = errorRoundSignature(errored.map(({ block, result }) => ({ name: block.name, error: String(result.error) })));
|
|
17013
|
+
if (errSignature && errSignature === lastErrorSignature) {
|
|
17014
|
+
repeatedErrorRounds++;
|
|
17015
|
+
} else {
|
|
17016
|
+
repeatedErrorRounds = 0;
|
|
17017
|
+
lastErrorSignature = errSignature;
|
|
17018
|
+
}
|
|
17019
|
+
if (repeatedErrorRounds >= REPEAT_STALL_LIMIT) {
|
|
17020
|
+
stalledOut = true;
|
|
17021
|
+
stalledRepeatError = errored[0] ? String(errored[0].result.error).slice(0, 300) : null;
|
|
17022
|
+
break;
|
|
17023
|
+
}
|
|
16584
17024
|
if (autoContinue && iteration + 1 >= budget && budget < hardCap) {
|
|
16585
17025
|
budget = Math.min(budget + maxIterations, hardCap);
|
|
16586
17026
|
options.onText(`
|
|
@@ -16590,11 +17030,15 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
|
|
|
16590
17030
|
}
|
|
16591
17031
|
if (!options.abortSignal?.aborted && !completedCleanly) {
|
|
16592
17032
|
if (stalledOut) {
|
|
16593
|
-
options.onText(`
|
|
17033
|
+
(options.onNotice ?? options.onText)(stalledRepeatError ? `
|
|
17034
|
+
\u{1F6D1} Stopped: the same tool error repeated ${REPEAT_STALL_LIMIT} rounds in a row, so the agent was looping without making progress. The recurring error was:
|
|
17035
|
+
${stalledRepeatError}
|
|
17036
|
+
Fix that underlying cause (or grant the needed permission) and send "continue".
|
|
17037
|
+
` : `
|
|
16594
17038
|
\u{1F6D1} Stopped: the last ${STALL_LIMIT} tool rounds all failed, so the agent looked stuck. Fix the underlying error (or grant the needed permission) and send "continue".
|
|
16595
17039
|
`);
|
|
16596
17040
|
} else if (iteration >= budget) {
|
|
16597
|
-
options.onText(`
|
|
17041
|
+
(options.onNotice ?? options.onText)(`
|
|
16598
17042
|
\u23F8\uFE0F Stopped at the ${budget}-step safety limit \u2014 the task may be incomplete. Send "continue" to resume, or raise the limit via "maxIterations" in .nexrall/settings.json (or the NEXRALL_MAX_ITERATIONS env var). Auto-continue can be disabled with "autoContinue": false.
|
|
16599
17043
|
`);
|
|
16600
17044
|
}
|
|
@@ -18482,6 +18926,8 @@ var require_dist2 = __commonJS({
|
|
|
18482
18926
|
__exportStar(require_manager2(), exports);
|
|
18483
18927
|
__exportStar(require_loader(), exports);
|
|
18484
18928
|
__exportStar(require_agentTypes(), exports);
|
|
18929
|
+
__exportStar(require_planMode(), exports);
|
|
18930
|
+
__exportStar(require_agentRegistry(), exports);
|
|
18485
18931
|
__exportStar(require_rules(), exports);
|
|
18486
18932
|
__exportStar(require_destructive(), exports);
|
|
18487
18933
|
__exportStar(require_plugins(), exports);
|
|
@@ -63558,7 +64004,7 @@ var InkReadlineAdapter = class extends EventEmitter3 {
|
|
|
63558
64004
|
};
|
|
63559
64005
|
|
|
63560
64006
|
// src/commands/chat.ts
|
|
63561
|
-
var CLI_VERSION = "0.5.
|
|
64007
|
+
var CLI_VERSION = "0.5.52";
|
|
63562
64008
|
var MODEL_LABELS = {
|
|
63563
64009
|
turbo: "Nexrall Turbo",
|
|
63564
64010
|
pro: "Nexrall Pro",
|
|
@@ -63829,6 +64275,12 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env3, nexrall
|
|
|
63829
64275
|
env: env3,
|
|
63830
64276
|
nexrallMd,
|
|
63831
64277
|
mode,
|
|
64278
|
+
// `/mode plan` previously only hinted to the backend — the footer said "plan
|
|
64279
|
+
// mode on" while every write tool stayed live, so the one mode whose entire
|
|
64280
|
+
// promise is "I will not touch anything" was the one not enforced. This
|
|
64281
|
+
// makes it real: the loop refuses mutating tools before the permission
|
|
64282
|
+
// prompt, and sub-agents inherit the lock.
|
|
64283
|
+
planMode: mode === "plan",
|
|
63832
64284
|
effort,
|
|
63833
64285
|
// The backend streams a cumulative output-token count (routes/code.js's
|
|
63834
64286
|
// sendProgress, throttled to ≤5/s) covering thinking, visible text AND
|
|
@@ -63982,6 +64434,7 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env3
|
|
|
63982
64434
|
env: env3,
|
|
63983
64435
|
nexrallMd,
|
|
63984
64436
|
mode,
|
|
64437
|
+
planMode: mode === "plan",
|
|
63985
64438
|
effort,
|
|
63986
64439
|
abortSignal: _abortSignal,
|
|
63987
64440
|
onText: (text) => {
|
|
@@ -65218,7 +65671,7 @@ function pluginSourceRemoveCommand(name, opts) {
|
|
|
65218
65671
|
|
|
65219
65672
|
// src/index.ts
|
|
65220
65673
|
var program2 = new Command();
|
|
65221
|
-
program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.
|
|
65674
|
+
program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.52").enablePositionalOptions();
|
|
65222
65675
|
program2.command("auth").description("Login to your Nexrall account").action(authCommand);
|
|
65223
65676
|
program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
|
|
65224
65677
|
program2.command("update").description("Update nex to the latest version").option("-c, --check", "Check for updates without installing").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexrall-code",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.52",
|
|
4
4
|
"description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"react": "^19.2.8",
|
|
42
42
|
"readline": "^1.3.0",
|
|
43
43
|
"string-width": "^7.2.0",
|
|
44
|
-
"@nexrall/code-core": "1.4.
|
|
44
|
+
"@nexrall/code-core": "1.4.27"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@aws-sdk/client-s3": "^3.600.0",
|