nexrall-code 0.5.47 → 0.5.48
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 +445 -95
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3156,6 +3156,8 @@ var require_auth = __commonJS({
|
|
|
3156
3156
|
exports.clearAuth = clearAuth2;
|
|
3157
3157
|
exports.isAuthenticated = isAuthenticated3;
|
|
3158
3158
|
exports.getToken = getToken;
|
|
3159
|
+
exports.getRefreshToken = getRefreshToken;
|
|
3160
|
+
exports.updateTokens = updateTokens;
|
|
3159
3161
|
var fs9 = __importStar(__require("fs"));
|
|
3160
3162
|
var path6 = __importStar(__require("path"));
|
|
3161
3163
|
var os6 = __importStar(__require("os"));
|
|
@@ -3205,6 +3207,34 @@ var require_auth = __commonJS({
|
|
|
3205
3207
|
const auth = loadAuth();
|
|
3206
3208
|
return auth?.token ?? null;
|
|
3207
3209
|
}
|
|
3210
|
+
function getRefreshToken() {
|
|
3211
|
+
const envToken = process.env.NEXRALL_TOKEN;
|
|
3212
|
+
if (envToken && envToken.trim().length > 0)
|
|
3213
|
+
return null;
|
|
3214
|
+
const auth = loadAuth();
|
|
3215
|
+
const rt = auth?.refreshToken;
|
|
3216
|
+
return typeof rt === "string" && rt.length > 0 ? rt : null;
|
|
3217
|
+
}
|
|
3218
|
+
function updateTokens(token, refreshToken) {
|
|
3219
|
+
let existing = {};
|
|
3220
|
+
try {
|
|
3221
|
+
if (fs9.existsSync(CONFIG_FILE)) {
|
|
3222
|
+
const parsed = JSON.parse(fs9.readFileSync(CONFIG_FILE, "utf-8"));
|
|
3223
|
+
if (typeof parsed === "object" && parsed !== null)
|
|
3224
|
+
existing = parsed;
|
|
3225
|
+
}
|
|
3226
|
+
} catch {
|
|
3227
|
+
}
|
|
3228
|
+
ensureConfigDir();
|
|
3229
|
+
const next = {
|
|
3230
|
+
...existing,
|
|
3231
|
+
token,
|
|
3232
|
+
// Keep the previous refresh token if the server didn't send a new one, rather
|
|
3233
|
+
// than deleting the only means of refreshing again.
|
|
3234
|
+
...refreshToken ? { refreshToken } : {}
|
|
3235
|
+
};
|
|
3236
|
+
fs9.writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2), { mode: 384 });
|
|
3237
|
+
}
|
|
3208
3238
|
}
|
|
3209
3239
|
});
|
|
3210
3240
|
|
|
@@ -9842,6 +9872,7 @@ var require_client = __commonJS({
|
|
|
9842
9872
|
exports.chooseFinalContent = chooseFinalContent;
|
|
9843
9873
|
exports.streamChat = streamChat;
|
|
9844
9874
|
exports.cancelTurn = cancelTurn;
|
|
9875
|
+
exports.revokeRefreshToken = revokeRefreshToken2;
|
|
9845
9876
|
exports.getBalance = getBalance3;
|
|
9846
9877
|
exports.exchangeVscodeCode = exchangeVscodeCode;
|
|
9847
9878
|
exports.login = login2;
|
|
@@ -9892,6 +9923,49 @@ var require_client = __commonJS({
|
|
|
9892
9923
|
Authorization: `Bearer ${token}`
|
|
9893
9924
|
};
|
|
9894
9925
|
}
|
|
9926
|
+
var _refreshInFlight = null;
|
|
9927
|
+
async function refreshAccessToken() {
|
|
9928
|
+
if (_refreshInFlight)
|
|
9929
|
+
return _refreshInFlight;
|
|
9930
|
+
_refreshInFlight = (async () => {
|
|
9931
|
+
const refreshToken = (0, index_1.getRefreshToken)();
|
|
9932
|
+
if (!refreshToken)
|
|
9933
|
+
return false;
|
|
9934
|
+
try {
|
|
9935
|
+
const res = await (0, node_fetch_1.default)(`${exports.API_BASE}/api/auth/refresh`, {
|
|
9936
|
+
method: "POST",
|
|
9937
|
+
headers: { "Content-Type": "application/json" },
|
|
9938
|
+
body: JSON.stringify({ refreshToken })
|
|
9939
|
+
});
|
|
9940
|
+
if (!res.ok)
|
|
9941
|
+
return false;
|
|
9942
|
+
const data = await res.json();
|
|
9943
|
+
if (!data.token)
|
|
9944
|
+
return false;
|
|
9945
|
+
(0, index_1.updateTokens)(data.token, data.refreshToken);
|
|
9946
|
+
return true;
|
|
9947
|
+
} catch {
|
|
9948
|
+
return false;
|
|
9949
|
+
}
|
|
9950
|
+
})();
|
|
9951
|
+
try {
|
|
9952
|
+
return await _refreshInFlight;
|
|
9953
|
+
} finally {
|
|
9954
|
+
_refreshInFlight = null;
|
|
9955
|
+
}
|
|
9956
|
+
}
|
|
9957
|
+
function isExpiredTokenResponse(status, body) {
|
|
9958
|
+
if (status !== 401 && status !== 403)
|
|
9959
|
+
return false;
|
|
9960
|
+
try {
|
|
9961
|
+
const parsed = JSON.parse(body);
|
|
9962
|
+
if (parsed?.error && typeof parsed.error === "object")
|
|
9963
|
+
return false;
|
|
9964
|
+
return typeof parsed?.error === "string" && /token|auth|expired/i.test(parsed.error);
|
|
9965
|
+
} catch {
|
|
9966
|
+
return false;
|
|
9967
|
+
}
|
|
9968
|
+
}
|
|
9895
9969
|
var MAX_RETRIES = 5;
|
|
9896
9970
|
var RETRY_BASE_MS = 1e3;
|
|
9897
9971
|
var RETRY_MAX_MS = 3e4;
|
|
@@ -9974,14 +10048,43 @@ var require_client = __commonJS({
|
|
|
9974
10048
|
onEvent({ type: "retry_resolved" });
|
|
9975
10049
|
}
|
|
9976
10050
|
};
|
|
10051
|
+
const prepareRestart = async (reason) => {
|
|
10052
|
+
await cancelTurn(turnId);
|
|
10053
|
+
turnId = (0, crypto_1.randomUUID)();
|
|
10054
|
+
resuming = false;
|
|
10055
|
+
serverResumable = false;
|
|
10056
|
+
lastEventId = 0;
|
|
10057
|
+
carryText = [];
|
|
10058
|
+
carryToolUse = [];
|
|
10059
|
+
const hadRendered = emittedAnythingAcrossAttempts;
|
|
10060
|
+
const renderedChars = emittedCharsAcrossAttempts;
|
|
10061
|
+
emittedAnythingAcrossAttempts = false;
|
|
10062
|
+
emittedCharsAcrossAttempts = 0;
|
|
10063
|
+
if (hadRendered && !allowRestartAfterRender)
|
|
10064
|
+
return false;
|
|
10065
|
+
if (hadRendered) {
|
|
10066
|
+
onEvent({ type: "stream_restart", reason, discardedChars: renderedChars });
|
|
10067
|
+
}
|
|
10068
|
+
return true;
|
|
10069
|
+
};
|
|
10070
|
+
const bodySaysNotResumable = (body) => {
|
|
10071
|
+
try {
|
|
10072
|
+
return JSON.parse(body)?.notResumable === true;
|
|
10073
|
+
} catch {
|
|
10074
|
+
return false;
|
|
10075
|
+
}
|
|
10076
|
+
};
|
|
9977
10077
|
async function runAttempt() {
|
|
9978
10078
|
let response;
|
|
9979
10079
|
let lastErr;
|
|
9980
10080
|
let inFlightWaits = 0;
|
|
9981
10081
|
let errorBodyOverride = null;
|
|
9982
10082
|
let forbiddenExhausted = false;
|
|
10083
|
+
let refreshedThisAttempt = false;
|
|
10084
|
+
let sessionExpired = false;
|
|
9983
10085
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
9984
10086
|
try {
|
|
10087
|
+
errorBodyOverride = null;
|
|
9985
10088
|
response = await (0, node_fetch_1.default)(...buildFetchArgs());
|
|
9986
10089
|
if (response.status === 429 && attempt < MAX_RETRIES && canRetry()) {
|
|
9987
10090
|
reportRetry("Rate limited by the API \u2014 retrying");
|
|
@@ -9994,9 +10097,30 @@ var require_client = __commonJS({
|
|
|
9994
10097
|
await sleepWithinBudget(backoffMs(attempt));
|
|
9995
10098
|
continue;
|
|
9996
10099
|
}
|
|
10100
|
+
if (response.status === 401 || response.status === 403) {
|
|
10101
|
+
const bodyText = await response.text().catch(() => "");
|
|
10102
|
+
if (isExpiredTokenResponse(response.status, bodyText)) {
|
|
10103
|
+
if (!refreshedThisAttempt) {
|
|
10104
|
+
refreshedThisAttempt = true;
|
|
10105
|
+
if (await refreshAccessToken()) {
|
|
10106
|
+
reportRetry("Session expired \u2014 signing you back in");
|
|
10107
|
+
attempt--;
|
|
10108
|
+
continue;
|
|
10109
|
+
}
|
|
10110
|
+
}
|
|
10111
|
+
sessionExpired = true;
|
|
10112
|
+
errorBodyOverride = JSON.stringify({
|
|
10113
|
+
// Replaces the backend's bare "Invalid or expired token", which reads
|
|
10114
|
+
// like a bug rather than something the user can act on.
|
|
10115
|
+
error: "Your session has expired. Run `nex login` to sign in again."
|
|
10116
|
+
});
|
|
10117
|
+
break;
|
|
10118
|
+
}
|
|
10119
|
+
errorBodyOverride = bodyText;
|
|
10120
|
+
}
|
|
9997
10121
|
const FORBIDDEN_RETRY_LIMIT = 2;
|
|
9998
10122
|
if (response.status === 403) {
|
|
9999
|
-
const bodyText = await response.text().catch(() => "");
|
|
10123
|
+
const bodyText = errorBodyOverride ?? await response.text().catch(() => "");
|
|
10000
10124
|
let isUpstreamForbidden = false;
|
|
10001
10125
|
try {
|
|
10002
10126
|
const parsed = JSON.parse(bodyText);
|
|
@@ -10016,35 +10140,22 @@ var require_client = __commonJS({
|
|
|
10016
10140
|
errorBodyOverride = bodyText;
|
|
10017
10141
|
break;
|
|
10018
10142
|
}
|
|
10019
|
-
if (response.status
|
|
10020
|
-
await response.text().catch(() => "");
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
throw new Error("Connection lost and this turn could no longer be resumed. Send your message again.");
|
|
10034
|
-
}
|
|
10035
|
-
if (hadRendered) {
|
|
10036
|
-
onEvent({
|
|
10037
|
-
type: "stream_restart",
|
|
10038
|
-
reason: "Connection lost too long to resume",
|
|
10039
|
-
discardedChars: renderedChars
|
|
10040
|
-
});
|
|
10041
|
-
}
|
|
10042
|
-
if (attempt < MAX_RETRIES && canRetry()) {
|
|
10043
|
-
reportRetry("Could not resume \u2014 restarting this turn");
|
|
10044
|
-
await sleepWithinBudget(backoffMs(attempt));
|
|
10045
|
-
continue;
|
|
10143
|
+
if (resuming && response.status >= 400 && response.status < 500) {
|
|
10144
|
+
const body = errorBodyOverride ?? await response.text().catch(() => "");
|
|
10145
|
+
if (response.status === 410 || response.status === 404 || bodySaysNotResumable(body)) {
|
|
10146
|
+
const canRestart = await prepareRestart("Connection lost too long to resume");
|
|
10147
|
+
if (!canRestart) {
|
|
10148
|
+
throw new Error("Connection lost and this turn could no longer be resumed. Send your message again.");
|
|
10149
|
+
}
|
|
10150
|
+
if (attempt < MAX_RETRIES && canRetry()) {
|
|
10151
|
+
reportRetry("Could not resume \u2014 restarting this turn");
|
|
10152
|
+
await sleepWithinBudget(backoffMs(attempt));
|
|
10153
|
+
continue;
|
|
10154
|
+
}
|
|
10155
|
+
errorBodyOverride = JSON.stringify({ error: "Could not resume this turn \u2014 send your message again." });
|
|
10156
|
+
break;
|
|
10046
10157
|
}
|
|
10047
|
-
errorBodyOverride =
|
|
10158
|
+
errorBodyOverride = body;
|
|
10048
10159
|
break;
|
|
10049
10160
|
}
|
|
10050
10161
|
if (response.status === 409) {
|
|
@@ -10096,7 +10207,12 @@ var require_client = __commonJS({
|
|
|
10096
10207
|
} catch {
|
|
10097
10208
|
errMsg = errText || errMsg;
|
|
10098
10209
|
}
|
|
10099
|
-
throw Object.assign(new Error(errMsg), {
|
|
10210
|
+
throw Object.assign(new Error(errMsg), {
|
|
10211
|
+
status: response.status,
|
|
10212
|
+
balance,
|
|
10213
|
+
...forbiddenExhausted ? { retryable: true } : {},
|
|
10214
|
+
...sessionExpired ? { authExpired: true } : {}
|
|
10215
|
+
});
|
|
10100
10216
|
}
|
|
10101
10217
|
if (!response.body) {
|
|
10102
10218
|
throw new Error("Response body is null");
|
|
@@ -10321,6 +10437,8 @@ var require_client = __commonJS({
|
|
|
10321
10437
|
stream.destroy?.();
|
|
10322
10438
|
reject(tagTransient(new Error(message)));
|
|
10323
10439
|
} else {
|
|
10440
|
+
clearInterval(heartbeatWatchdog);
|
|
10441
|
+
stream.destroy?.();
|
|
10324
10442
|
onEvent({ type: "error", message });
|
|
10325
10443
|
reject(new Error(message));
|
|
10326
10444
|
}
|
|
@@ -10381,27 +10499,10 @@ var require_client = __commonJS({
|
|
|
10381
10499
|
const e2 = err;
|
|
10382
10500
|
if (e2.retryable && sAttempt < MAX_RETRIES && canRetry()) {
|
|
10383
10501
|
if (e2.forceRestart) {
|
|
10384
|
-
await
|
|
10385
|
-
|
|
10386
|
-
resuming = false;
|
|
10387
|
-
serverResumable = false;
|
|
10388
|
-
lastEventId = 0;
|
|
10389
|
-
carryText = [];
|
|
10390
|
-
carryToolUse = [];
|
|
10391
|
-
const hadRendered = emittedAnythingAcrossAttempts;
|
|
10392
|
-
const renderedChars = emittedCharsAcrossAttempts;
|
|
10393
|
-
emittedAnythingAcrossAttempts = false;
|
|
10394
|
-
emittedCharsAcrossAttempts = 0;
|
|
10395
|
-
if (hadRendered && !allowRestartAfterRender) {
|
|
10502
|
+
const canRestart = await prepareRestart(err.message || "Could not resume \u2014 restarting this turn");
|
|
10503
|
+
if (!canRestart) {
|
|
10396
10504
|
throw new Error("Connection lost and this turn could no longer be resumed. Send your message again.");
|
|
10397
10505
|
}
|
|
10398
|
-
if (hadRendered) {
|
|
10399
|
-
onEvent({
|
|
10400
|
-
type: "stream_restart",
|
|
10401
|
-
reason: err.message || "Could not resume \u2014 restarting this turn",
|
|
10402
|
-
discardedChars: renderedChars
|
|
10403
|
-
});
|
|
10404
|
-
}
|
|
10405
10506
|
reportRetry("Could not resume \u2014 restarting this turn");
|
|
10406
10507
|
await sleepWithinBudget(backoffMs(sAttempt));
|
|
10407
10508
|
continue;
|
|
@@ -10445,11 +10546,30 @@ var require_client = __commonJS({
|
|
|
10445
10546
|
} catch {
|
|
10446
10547
|
}
|
|
10447
10548
|
}
|
|
10549
|
+
async function revokeRefreshToken2() {
|
|
10550
|
+
const refreshToken = (0, index_1.getRefreshToken)();
|
|
10551
|
+
if (!refreshToken)
|
|
10552
|
+
return;
|
|
10553
|
+
try {
|
|
10554
|
+
await (0, node_fetch_1.default)(`${exports.API_BASE}/api/auth/logout`, {
|
|
10555
|
+
method: "POST",
|
|
10556
|
+
headers: { "Content-Type": "application/json" },
|
|
10557
|
+
body: JSON.stringify({ refreshToken })
|
|
10558
|
+
});
|
|
10559
|
+
} catch {
|
|
10560
|
+
}
|
|
10561
|
+
}
|
|
10448
10562
|
async function getBalance3() {
|
|
10449
|
-
const
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
|
|
10563
|
+
const fetchOnce = () => (0, node_fetch_1.default)(`${exports.API_BASE}/api/code/balance`, { method: "GET", headers: authHeaders() });
|
|
10564
|
+
let response = await fetchOnce();
|
|
10565
|
+
if (response.status === 401 || response.status === 403) {
|
|
10566
|
+
const body = await response.text().catch(() => "");
|
|
10567
|
+
if (isExpiredTokenResponse(response.status, body) && await refreshAccessToken()) {
|
|
10568
|
+
response = await fetchOnce();
|
|
10569
|
+
} else {
|
|
10570
|
+
throw new Error(`API error ${response.status}: ${body}`);
|
|
10571
|
+
}
|
|
10572
|
+
}
|
|
10453
10573
|
if (!response.ok) {
|
|
10454
10574
|
const errText = await response.text();
|
|
10455
10575
|
throw new Error(`API error ${response.status}: ${errText}`);
|
|
@@ -10470,7 +10590,11 @@ var require_client = __commonJS({
|
|
|
10470
10590
|
const data = await response.json();
|
|
10471
10591
|
if (!data.token)
|
|
10472
10592
|
throw new Error("No token in exchange response");
|
|
10473
|
-
return {
|
|
10593
|
+
return {
|
|
10594
|
+
token: data.token,
|
|
10595
|
+
...data.refreshToken ? { refreshToken: data.refreshToken } : {},
|
|
10596
|
+
email: data.user?.email ?? ""
|
|
10597
|
+
};
|
|
10474
10598
|
}
|
|
10475
10599
|
async function login2(email, password) {
|
|
10476
10600
|
const response = await (0, node_fetch_1.default)(`${exports.API_BASE}/api/auth/login/email`, {
|
|
@@ -10486,7 +10610,11 @@ var require_client = __commonJS({
|
|
|
10486
10610
|
if (!data.token) {
|
|
10487
10611
|
throw new Error("Login response missing token");
|
|
10488
10612
|
}
|
|
10489
|
-
return {
|
|
10613
|
+
return {
|
|
10614
|
+
token: data.token,
|
|
10615
|
+
...data.refreshToken ? { refreshToken: data.refreshToken } : {},
|
|
10616
|
+
email: data.email ?? email
|
|
10617
|
+
};
|
|
10490
10618
|
}
|
|
10491
10619
|
}
|
|
10492
10620
|
});
|
|
@@ -12606,13 +12734,7 @@ Resolve the conflict (remove <<<<<<< / ======= / >>>>>>> markers and keep the in
|
|
|
12606
12734
|
} catch {
|
|
12607
12735
|
}
|
|
12608
12736
|
}
|
|
12609
|
-
|
|
12610
|
-
if (existingMode !== void 0) {
|
|
12611
|
-
try {
|
|
12612
|
-
fs9.chmodSync(resolved, existingMode);
|
|
12613
|
-
} catch {
|
|
12614
|
-
}
|
|
12615
|
-
}
|
|
12737
|
+
atomicWrite(resolved, content, existingMode);
|
|
12616
12738
|
const bytes = Buffer.byteLength(content, "utf-8");
|
|
12617
12739
|
const lines = content.split("\n").length;
|
|
12618
12740
|
if (isNew) {
|
|
@@ -13326,14 +13448,52 @@ ${globalCapMatches(output)}` : "";
|
|
|
13326
13448
|
}
|
|
13327
13449
|
return { mode: stat2.mode, content: fs9.readFileSync(resolved, "utf-8") };
|
|
13328
13450
|
}
|
|
13329
|
-
function
|
|
13330
|
-
|
|
13331
|
-
fs9.writeFileSync(tmp, data, "utf-8");
|
|
13451
|
+
function atomicWrite(resolved, data, mode) {
|
|
13452
|
+
let target = resolved;
|
|
13332
13453
|
try {
|
|
13333
|
-
fs9.
|
|
13454
|
+
if (fs9.lstatSync(resolved).isSymbolicLink())
|
|
13455
|
+
target = fs9.realpathSync(resolved);
|
|
13334
13456
|
} catch {
|
|
13335
13457
|
}
|
|
13336
|
-
|
|
13458
|
+
const tmp = `${target}.nexrall_tmp_${process.pid}`;
|
|
13459
|
+
try {
|
|
13460
|
+
fs9.writeFileSync(tmp, data, "utf-8");
|
|
13461
|
+
} catch (err) {
|
|
13462
|
+
const code = err.code;
|
|
13463
|
+
if (code === "EACCES" || code === "EPERM" || code === "EROFS") {
|
|
13464
|
+
fs9.writeFileSync(target, data, "utf-8");
|
|
13465
|
+
if (mode !== void 0) {
|
|
13466
|
+
try {
|
|
13467
|
+
fs9.chmodSync(target, mode);
|
|
13468
|
+
} catch {
|
|
13469
|
+
}
|
|
13470
|
+
}
|
|
13471
|
+
return;
|
|
13472
|
+
}
|
|
13473
|
+
try {
|
|
13474
|
+
fs9.rmSync(tmp, { force: true });
|
|
13475
|
+
} catch {
|
|
13476
|
+
}
|
|
13477
|
+
throw err;
|
|
13478
|
+
}
|
|
13479
|
+
try {
|
|
13480
|
+
if (mode !== void 0) {
|
|
13481
|
+
try {
|
|
13482
|
+
fs9.chmodSync(tmp, mode);
|
|
13483
|
+
} catch {
|
|
13484
|
+
}
|
|
13485
|
+
}
|
|
13486
|
+
fs9.renameSync(tmp, target);
|
|
13487
|
+
} catch (err) {
|
|
13488
|
+
try {
|
|
13489
|
+
fs9.rmSync(tmp, { force: true });
|
|
13490
|
+
} catch {
|
|
13491
|
+
}
|
|
13492
|
+
throw err;
|
|
13493
|
+
}
|
|
13494
|
+
}
|
|
13495
|
+
function atomicWritePreservingMode(resolved, data, mode) {
|
|
13496
|
+
atomicWrite(resolved, data, mode);
|
|
13337
13497
|
}
|
|
13338
13498
|
function normalizeLF(s2) {
|
|
13339
13499
|
return s2.replace(/\r\n/g, "\n");
|
|
@@ -14738,6 +14898,10 @@ var require_loop = __commonJS({
|
|
|
14738
14898
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14739
14899
|
exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = void 0;
|
|
14740
14900
|
exports.resolveMaxIterations = resolveMaxIterations;
|
|
14901
|
+
exports.createLimiter = createLimiter;
|
|
14902
|
+
exports.extractSubTaskText = extractSubTaskText;
|
|
14903
|
+
exports.capSubTaskText = capSubTaskText;
|
|
14904
|
+
exports.summariseSubTaskProgress = summariseSubTaskProgress;
|
|
14741
14905
|
exports.contextWindowFor = contextWindowFor2;
|
|
14742
14906
|
exports.compactionThresholds = compactionThresholds2;
|
|
14743
14907
|
exports.estimateBodyBytes = estimateBodyBytes2;
|
|
@@ -14900,6 +15064,29 @@ var require_loop = __commonJS({
|
|
|
14900
15064
|
_fileLocks.delete(absPath);
|
|
14901
15065
|
}
|
|
14902
15066
|
}
|
|
15067
|
+
var MAX_CONCURRENT_SUBTASKS = (() => {
|
|
15068
|
+
const raw = Number(process.env.NEXRALL_MAX_CONCURRENT_SUBTASKS);
|
|
15069
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 4;
|
|
15070
|
+
})();
|
|
15071
|
+
function createLimiter(max) {
|
|
15072
|
+
let active = 0;
|
|
15073
|
+
const queue = [];
|
|
15074
|
+
const release2 = () => {
|
|
15075
|
+
active--;
|
|
15076
|
+
queue.shift()?.();
|
|
15077
|
+
};
|
|
15078
|
+
return async (fn) => {
|
|
15079
|
+
if (active >= max)
|
|
15080
|
+
await new Promise((resolve3) => queue.push(resolve3));
|
|
15081
|
+
active++;
|
|
15082
|
+
try {
|
|
15083
|
+
return await fn();
|
|
15084
|
+
} finally {
|
|
15085
|
+
release2();
|
|
15086
|
+
}
|
|
15087
|
+
};
|
|
15088
|
+
}
|
|
15089
|
+
var _subTaskLimit = createLimiter(MAX_CONCURRENT_SUBTASKS);
|
|
14903
15090
|
function humanDescription(name, input) {
|
|
14904
15091
|
switch (name) {
|
|
14905
15092
|
case "read_file":
|
|
@@ -14981,16 +15168,71 @@ var require_loop = __commonJS({
|
|
|
14981
15168
|
return `Use tool: ${name}`;
|
|
14982
15169
|
}
|
|
14983
15170
|
}
|
|
14984
|
-
var MAX_TASK_DEPTH =
|
|
15171
|
+
var MAX_TASK_DEPTH = 1;
|
|
14985
15172
|
var _subTaskCounter = 0;
|
|
14986
15173
|
var SUBTASK_TIMEOUT_MS = Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS) > 0 ? Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS) : 10 * 60 * 1e3;
|
|
15174
|
+
var SUBTASK_MAX = 48e3;
|
|
15175
|
+
function sliceSafeEnd(s2, max) {
|
|
15176
|
+
if (s2.length <= max)
|
|
15177
|
+
return s2;
|
|
15178
|
+
let end = max;
|
|
15179
|
+
const code = s2.charCodeAt(end - 1);
|
|
15180
|
+
if (code >= 55296 && code <= 56319)
|
|
15181
|
+
end--;
|
|
15182
|
+
return s2.slice(0, end);
|
|
15183
|
+
}
|
|
15184
|
+
function sliceSafeStart(s2, from) {
|
|
15185
|
+
if (from <= 0)
|
|
15186
|
+
return s2;
|
|
15187
|
+
let start = from;
|
|
15188
|
+
const code = s2.charCodeAt(start);
|
|
15189
|
+
if (code >= 56320 && code <= 57343)
|
|
15190
|
+
start++;
|
|
15191
|
+
return s2.slice(start);
|
|
15192
|
+
}
|
|
15193
|
+
function extractSubTaskText(messages, preferLast = true) {
|
|
15194
|
+
const assistants = messages.filter((m2) => m2.role === "assistant");
|
|
15195
|
+
const textOf = (m2) => (m2?.content ?? []).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("").trim();
|
|
15196
|
+
if (preferLast)
|
|
15197
|
+
return textOf(assistants[assistants.length - 1]);
|
|
15198
|
+
return assistants.map(textOf).filter(Boolean).join("\n\n").trim();
|
|
15199
|
+
}
|
|
15200
|
+
function capSubTaskText(text, max = SUBTASK_MAX) {
|
|
15201
|
+
if (text.length <= max)
|
|
15202
|
+
return text;
|
|
15203
|
+
const head = sliceSafeEnd(text, Math.floor(max * 0.6));
|
|
15204
|
+
const tail = sliceSafeStart(text, text.length - Math.floor(max * 0.4));
|
|
15205
|
+
return `${head}
|
|
15206
|
+
|
|
15207
|
+
[\u2026 sub-task output truncated (${text.length} chars) \u2014 kept the beginning and end \u2026]
|
|
15208
|
+
|
|
15209
|
+
${tail}`;
|
|
15210
|
+
}
|
|
15211
|
+
function summariseSubTaskProgress(messages) {
|
|
15212
|
+
const toolNames = [];
|
|
15213
|
+
for (const m2 of messages) {
|
|
15214
|
+
if (m2.role !== "assistant" || !Array.isArray(m2.content))
|
|
15215
|
+
continue;
|
|
15216
|
+
for (const b of m2.content) {
|
|
15217
|
+
if (b?.type === "tool_use" && typeof b.name === "string")
|
|
15218
|
+
toolNames.push(b.name);
|
|
15219
|
+
}
|
|
15220
|
+
}
|
|
15221
|
+
if (toolNames.length === 0)
|
|
15222
|
+
return "";
|
|
15223
|
+
const counts = /* @__PURE__ */ new Map();
|
|
15224
|
+
for (const n of toolNames)
|
|
15225
|
+
counts.set(n, (counts.get(n) ?? 0) + 1);
|
|
15226
|
+
const inventory = [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([name, n]) => n > 1 ? `${name} \xD7${n}` : name).join(", ");
|
|
15227
|
+
return `Tool calls completed before it was stopped (${toolNames.length} total): ${inventory}.`;
|
|
15228
|
+
}
|
|
14987
15229
|
async function runSubTask(input, options, agentTypes) {
|
|
14988
15230
|
const prompt2 = typeof input.prompt === "string" ? input.prompt.trim() : "";
|
|
14989
15231
|
if (!prompt2)
|
|
14990
15232
|
return { error: "task tool requires a non-empty prompt" };
|
|
14991
15233
|
const depth = options._depth ?? 0;
|
|
14992
15234
|
if (depth >= MAX_TASK_DEPTH) {
|
|
14993
|
-
return { error:
|
|
15235
|
+
return { error: "Sub-agents cannot spawn further sub-agents. Do this work directly, or report back so the main agent can delegate it." };
|
|
14994
15236
|
}
|
|
14995
15237
|
const requestedType = typeof input.subagent_type === "string" ? input.subagent_type : "";
|
|
14996
15238
|
const agent = (0, agentTypes_1.findAgentType)(agentTypes, requestedType);
|
|
@@ -15064,22 +15306,36 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
15064
15306
|
onThinkingProgress: (tok) => options.onThinkingProgress?.(tok)
|
|
15065
15307
|
});
|
|
15066
15308
|
if (subAbort.aborted && !options.abortSignal?.aborted) {
|
|
15067
|
-
|
|
15068
|
-
|
|
15069
|
-
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
|
|
15073
|
-
|
|
15074
|
-
|
|
15075
|
-
|
|
15076
|
-
|
|
15077
|
-
|
|
15078
|
-
|
|
15079
|
-
|
|
15080
|
-
|
|
15309
|
+
const mins = Math.round(SUBTASK_TIMEOUT_MS / 6e4);
|
|
15310
|
+
const partial = capSubTaskText(extractSubTaskText(result, false));
|
|
15311
|
+
const progress = summariseSubTaskProgress(result);
|
|
15312
|
+
const sections = [
|
|
15313
|
+
`Sub-task STOPPED after ${mins} minutes without completing \u2014 treat the following as PARTIAL, unverified work, not a finished answer.`,
|
|
15314
|
+
progress,
|
|
15315
|
+
partial ? `Partial output before it was stopped:
|
|
15316
|
+
|
|
15317
|
+
${partial}` : "",
|
|
15318
|
+
"Do NOT simply re-run the same sub-task: build on what is above, or split the remaining work into smaller, more focused sub-tasks."
|
|
15319
|
+
].filter(Boolean);
|
|
15320
|
+
return { error: sections.join("\n\n") };
|
|
15321
|
+
}
|
|
15322
|
+
const text = capSubTaskText(extractSubTaskText(result, true));
|
|
15081
15323
|
return { output: text || "(sub-task completed with no text output)" };
|
|
15082
15324
|
} catch (err) {
|
|
15325
|
+
const salvaged = (0, types_1.salvageHistory)(err);
|
|
15326
|
+
if (salvaged) {
|
|
15327
|
+
const partial = capSubTaskText(extractSubTaskText(salvaged, false));
|
|
15328
|
+
const progress = summariseSubTaskProgress(salvaged);
|
|
15329
|
+
const sections = [
|
|
15330
|
+
`Sub-task FAILED before completing: ${err.message}`,
|
|
15331
|
+
progress,
|
|
15332
|
+
partial ? `Partial output before the failure:
|
|
15333
|
+
|
|
15334
|
+
${partial}` : "",
|
|
15335
|
+
"Treat the above as PARTIAL, unverified work. Build on it rather than re-running the whole sub-task."
|
|
15336
|
+
].filter(Boolean);
|
|
15337
|
+
return { error: sections.join("\n\n") };
|
|
15338
|
+
}
|
|
15083
15339
|
return { error: `Sub-task failed: ${err.message}` };
|
|
15084
15340
|
} finally {
|
|
15085
15341
|
clearTimeout(timer);
|
|
@@ -15678,7 +15934,7 @@ Continue the work from here.` }] });
|
|
|
15678
15934
|
if (!permitted) {
|
|
15679
15935
|
result = { error: "Permission denied by user" };
|
|
15680
15936
|
} else if (name === "task") {
|
|
15681
|
-
result = await runSubTask(input, options, agentTypes);
|
|
15937
|
+
result = depth === 0 ? await _subTaskLimit(() => runSubTask(input, options, agentTypes)) : await runSubTask(input, options, agentTypes);
|
|
15682
15938
|
} else {
|
|
15683
15939
|
const pre = runToolHooks(hooks.PreToolUse, "PreToolUse", name, input, options.workDir);
|
|
15684
15940
|
if (pre.block) {
|
|
@@ -51904,6 +52160,7 @@ async function logoutCommand() {
|
|
|
51904
52160
|
console.log();
|
|
51905
52161
|
return;
|
|
51906
52162
|
}
|
|
52163
|
+
await (0, import_code_core.revokeRefreshToken)();
|
|
51907
52164
|
(0, import_code_core.clearAuth)();
|
|
51908
52165
|
console.log(source_default.green(" \u2713 Logged out."));
|
|
51909
52166
|
console.log();
|
|
@@ -62537,7 +62794,7 @@ var InkReadlineAdapter = class extends EventEmitter3 {
|
|
|
62537
62794
|
};
|
|
62538
62795
|
|
|
62539
62796
|
// src/commands/chat.ts
|
|
62540
|
-
var CLI_VERSION = "0.5.
|
|
62797
|
+
var CLI_VERSION = "0.5.48";
|
|
62541
62798
|
var MODEL_LABELS = {
|
|
62542
62799
|
turbo: "Nexrall Turbo",
|
|
62543
62800
|
pro: "Nexrall Pro",
|
|
@@ -62622,28 +62879,83 @@ ${content}`);
|
|
|
62622
62879
|
}
|
|
62623
62880
|
return parts.length > 0 ? parts.join("\n\n---\n\n") : void 0;
|
|
62624
62881
|
}
|
|
62882
|
+
var THINK_TAIL_CHARS = 48;
|
|
62883
|
+
function composeStatusLine(opts) {
|
|
62884
|
+
const timer = source_default.dim(` (${opts.elapsedSec}s)`);
|
|
62885
|
+
const hint = opts.hint !== false && !opts.detail && opts.elapsedSec >= 15 ? source_default.dim(" \xB7 still running, no output yet is normal for quiet commands") : "";
|
|
62886
|
+
const detail = opts.detail ? source_default.dim(` ${opts.detail}`) : "";
|
|
62887
|
+
return `${source_default.cyan(opts.frame)} ${source_default.dim(opts.text)}${timer}${detail}${hint}`;
|
|
62888
|
+
}
|
|
62889
|
+
function formatProgressTokens(tokens) {
|
|
62890
|
+
if (!Number.isFinite(tokens) || tokens <= 0)
|
|
62891
|
+
return "";
|
|
62892
|
+
return tokens >= 1e3 ? `~${(tokens / 1e3).toFixed(1)}k tokens` : `~${tokens} tokens`;
|
|
62893
|
+
}
|
|
62625
62894
|
var Spinner = class {
|
|
62626
62895
|
interval = null;
|
|
62627
62896
|
frames = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
|
|
62628
62897
|
i = 0;
|
|
62629
62898
|
startedAt = 0;
|
|
62630
62899
|
baseText = "";
|
|
62631
|
-
|
|
62900
|
+
// Extra live detail appended after the elapsed timer (e.g. "~1.2k tokens").
|
|
62901
|
+
// Kept separate from `baseText` and mutated through setDetail() so a stream of
|
|
62902
|
+
// progress events can refresh it WITHOUT restarting the spinner — calling
|
|
62903
|
+
// start() again would reset `startedAt` and the elapsed counter would sit at 0s
|
|
62904
|
+
// forever, which is precisely the "is it frozen?" signal this class exists to
|
|
62905
|
+
// remove.
|
|
62906
|
+
detail = "";
|
|
62907
|
+
// Whether the reassurance hint applies. Off for phases where slowness is
|
|
62908
|
+
// already explained by the detail text (e.g. a live token counter is itself
|
|
62909
|
+
// proof of life, so "no output yet is normal" would be noise).
|
|
62910
|
+
hintEnabled = true;
|
|
62911
|
+
start(text, opts) {
|
|
62632
62912
|
this.stop();
|
|
62633
62913
|
this.baseText = text;
|
|
62914
|
+
this.detail = "";
|
|
62915
|
+
this.hintEnabled = opts?.hint !== false;
|
|
62634
62916
|
this.startedAt = Date.now();
|
|
62635
|
-
this.
|
|
62636
|
-
|
|
62637
|
-
|
|
62638
|
-
|
|
62639
|
-
|
|
62640
|
-
|
|
62641
|
-
|
|
62917
|
+
this.render();
|
|
62918
|
+
this.interval = setInterval(() => this.render(), 100);
|
|
62919
|
+
}
|
|
62920
|
+
/**
|
|
62921
|
+
* Replace the trailing detail text in place, keeping the elapsed timer running.
|
|
62922
|
+
*
|
|
62923
|
+
* No-op when the spinner isn't active: progress events can arrive a tick after
|
|
62924
|
+
* something else (a tool row, the first text delta) legitimately stopped it, and
|
|
62925
|
+
* resurrecting the spinner there would fight the printed output for the status line.
|
|
62926
|
+
*/
|
|
62927
|
+
setDetail(detail) {
|
|
62928
|
+
if (this.interval === null)
|
|
62929
|
+
return;
|
|
62930
|
+
if (this.detail === detail)
|
|
62931
|
+
return;
|
|
62932
|
+
this.detail = detail;
|
|
62933
|
+
this.render();
|
|
62934
|
+
}
|
|
62935
|
+
/** Swap the label without resetting the elapsed timer (phase change within one turn). */
|
|
62936
|
+
setText(text) {
|
|
62937
|
+
if (this.interval === null)
|
|
62938
|
+
return;
|
|
62939
|
+
if (this.baseText === text)
|
|
62940
|
+
return;
|
|
62941
|
+
this.baseText = text;
|
|
62942
|
+
this.render();
|
|
62943
|
+
}
|
|
62944
|
+
render() {
|
|
62945
|
+
getInkTerminal()?.setLive(composeStatusLine({
|
|
62946
|
+
frame: this.frames[this.i % this.frames.length],
|
|
62947
|
+
text: this.baseText,
|
|
62948
|
+
elapsedSec: Math.floor((Date.now() - this.startedAt) / 1e3),
|
|
62949
|
+
detail: this.detail,
|
|
62950
|
+
hint: this.hintEnabled
|
|
62951
|
+
}));
|
|
62952
|
+
this.i++;
|
|
62642
62953
|
}
|
|
62643
62954
|
stop() {
|
|
62644
62955
|
if (this.interval !== null) {
|
|
62645
62956
|
clearInterval(this.interval);
|
|
62646
62957
|
this.interval = null;
|
|
62958
|
+
this.detail = "";
|
|
62647
62959
|
getInkTerminal()?.setLive("");
|
|
62648
62960
|
}
|
|
62649
62961
|
}
|
|
@@ -62719,8 +63031,26 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env3, nexrall
|
|
|
62719
63031
|
let toolStartTime = 0;
|
|
62720
63032
|
let lastToolName = "";
|
|
62721
63033
|
let thinkingTokens = 0;
|
|
63034
|
+
let liveThinkTail = "";
|
|
63035
|
+
const pushStatus = () => {
|
|
63036
|
+
const parts = [];
|
|
63037
|
+
const tok = formatProgressTokens(thinkingTokens);
|
|
63038
|
+
if (tok)
|
|
63039
|
+
parts.push(tok);
|
|
63040
|
+
if (liveThinkTail)
|
|
63041
|
+
parts.push(liveThinkTail);
|
|
63042
|
+
spinner.setDetail(parts.join(" \xB7 "));
|
|
63043
|
+
};
|
|
63044
|
+
const resumeWorking = (label = "working\u2026") => {
|
|
63045
|
+
if (abortSignal.aborted)
|
|
63046
|
+
return;
|
|
63047
|
+
thinkingTokens = 0;
|
|
63048
|
+
liveThinkTail = "";
|
|
63049
|
+
spinner.start(label, { hint: false });
|
|
63050
|
+
};
|
|
62722
63051
|
setMode(mode);
|
|
62723
63052
|
console.log();
|
|
63053
|
+
spinner.start("thinking\u2026", { hint: false });
|
|
62724
63054
|
const result = await (0, import_code_core3.runAgentLoop)(messages, {
|
|
62725
63055
|
workDir,
|
|
62726
63056
|
model: modelAlias,
|
|
@@ -62728,10 +63058,26 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env3, nexrall
|
|
|
62728
63058
|
nexrallMd,
|
|
62729
63059
|
mode,
|
|
62730
63060
|
effort,
|
|
63061
|
+
// The backend streams a cumulative output-token count (routes/code.js's
|
|
63062
|
+
// sendProgress, throttled to ≤5/s) covering thinking, visible text AND
|
|
63063
|
+
// tool-argument JSON. This used to be stored in a variable and rendered only
|
|
63064
|
+
// at message_complete — i.e. after the turn was already over — so during the
|
|
63065
|
+
// long phase it exists to describe, it showed nothing at all.
|
|
62731
63066
|
onThinkingProgress: (tokens) => {
|
|
62732
63067
|
thinkingTokens = tokens;
|
|
63068
|
+
pushStatus();
|
|
62733
63069
|
},
|
|
62734
|
-
|
|
63070
|
+
// Live thinking text. Previously a hard no-op with the note "shown on
|
|
63071
|
+
// onThinking" — but onThinking only fires at message_complete, so a long
|
|
63072
|
+
// reasoning phase rendered nothing whatsoever until it had finished. Show a
|
|
63073
|
+
// short rolling tail on the status line so the user can see it actively
|
|
63074
|
+
// reasoning, then let onThinking print the proper summary block at the end.
|
|
63075
|
+
onThinkingDelta: (text) => {
|
|
63076
|
+
if (abortSignal.aborted)
|
|
63077
|
+
return;
|
|
63078
|
+
const flat = text.replace(/\s+/g, " ");
|
|
63079
|
+
liveThinkTail = (liveThinkTail + flat).slice(-THINK_TAIL_CHARS);
|
|
63080
|
+
pushStatus();
|
|
62735
63081
|
},
|
|
62736
63082
|
onThinking: (text) => {
|
|
62737
63083
|
if (abortSignal.aborted)
|
|
@@ -62739,6 +63085,8 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env3, nexrall
|
|
|
62739
63085
|
spinner.stop();
|
|
62740
63086
|
console.log(formatThinking(text, thinkingTokens));
|
|
62741
63087
|
thinkingTokens = 0;
|
|
63088
|
+
liveThinkTail = "";
|
|
63089
|
+
resumeWorking();
|
|
62742
63090
|
},
|
|
62743
63091
|
onText: (text) => {
|
|
62744
63092
|
if (abortSignal.aborted)
|
|
@@ -62757,6 +63105,7 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env3, nexrall
|
|
|
62757
63105
|
mdRender.flush();
|
|
62758
63106
|
spinner.stop();
|
|
62759
63107
|
console.log(source_default.dim(` ${text}`));
|
|
63108
|
+
resumeWorking();
|
|
62760
63109
|
},
|
|
62761
63110
|
onToolUse: (name, input) => {
|
|
62762
63111
|
if (abortSignal.aborted)
|
|
@@ -62787,6 +63136,7 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env3, nexrall
|
|
|
62787
63136
|
const durationMs = Date.now() - toolStartTime;
|
|
62788
63137
|
console.log(formatToolResult(lastToolName, res, durationMs));
|
|
62789
63138
|
toolStartTime = 0;
|
|
63139
|
+
resumeWorking();
|
|
62790
63140
|
},
|
|
62791
63141
|
// Ignore a `partial` report: it belongs to a cut-short attempt that was restarted,
|
|
62792
63142
|
// and the replacement attempt reports the turn's real totals. Letting it through
|
|
@@ -63989,7 +64339,7 @@ function pluginListCommand() {
|
|
|
63989
64339
|
|
|
63990
64340
|
// src/index.ts
|
|
63991
64341
|
var program2 = new Command();
|
|
63992
|
-
program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.
|
|
64342
|
+
program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.48");
|
|
63993
64343
|
program2.command("auth").description("Login to your Nexrall account").action(authCommand);
|
|
63994
64344
|
program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
|
|
63995
64345
|
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.48",
|
|
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.23"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@aws-sdk/client-s3": "^3.600.0",
|