nexrall-code 0.5.70 → 0.5.72

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.
Files changed (2) hide show
  1. package/dist/index.js +124 -29
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -9973,6 +9973,36 @@ var require_client = __commonJS({
9973
9973
  const raw = Number(process.env.NEXRALL_MAX_RETRY_MS);
9974
9974
  return Number.isFinite(raw) && raw > 0 ? raw : 5 * 6e4;
9975
9975
  })();
9976
+ var headerTimeoutMs = () => Number(process.env.NEXRALL_HEADER_TIMEOUT_MS) || 12e4;
9977
+ async function fetchWithHeaderTimeout(url, init, outerSignal, timeoutMs = headerTimeoutMs()) {
9978
+ const headerController = new AbortController();
9979
+ const onOuterAbort = () => headerController.abort();
9980
+ if (outerSignal.aborted)
9981
+ headerController.abort();
9982
+ else
9983
+ outerSignal.addEventListener("abort", onOuterAbort, { once: true });
9984
+ let timedOut = false;
9985
+ const timer = setTimeout(() => {
9986
+ timedOut = true;
9987
+ headerController.abort();
9988
+ }, timeoutMs);
9989
+ try {
9990
+ return await (0, node_fetch_1.default)(url, { ...init, signal: headerController.signal });
9991
+ } catch (err) {
9992
+ if (timedOut && !outerSignal.aborted) {
9993
+ throw Object.assign(
9994
+ new Error(`The server accepted the connection but sent no response within ${Math.round(timeoutMs / 1e3)} s. Reconnecting\u2026`),
9995
+ // Retryable by definition: nothing was received, so a fresh attempt
9996
+ // cannot duplicate any rendered output.
9997
+ { retryable: true }
9998
+ );
9999
+ }
10000
+ throw err;
10001
+ } finally {
10002
+ clearTimeout(timer);
10003
+ outerSignal.removeEventListener("abort", onOuterAbort);
10004
+ }
10005
+ }
9976
10006
  function backoffMs(attempt) {
9977
10007
  const exp = Math.min(RETRY_MAX_MS, RETRY_BASE_MS * Math.pow(2, attempt));
9978
10008
  return Math.round(exp / 2 + Math.random() * (exp / 2));
@@ -9982,7 +10012,7 @@ var require_client = __commonJS({
9982
10012
  }
9983
10013
  var MAX_TOTAL_ATTEMPTS = (MAX_RETRIES + 1) * (MAX_RETRIES + 1);
9984
10014
  async function streamChat(messages, options, onEvent) {
9985
- const { model, env: env3, editorContext, nexrallMd, mode, effort, clientType, abortSignal, extraTools, agents, skills, allowRestartAfterRender, canWriteSharedMemory, hasOwnAgentStore, canSpawnSubAgents } = options;
10015
+ const { model, env: env3, editorContext, nexrallMd, mode, effort, clientType, abortSignal, extraTools, agents, skills, allowRestartAfterRender, canWriteSharedMemory, hasOwnAgentStore, canSpawnSubAgents, canBrowseWeb } = options;
9986
10016
  let turnId = (0, crypto_1.randomUUID)();
9987
10017
  const controller = new AbortController();
9988
10018
  if (abortSignal?.aborted)
@@ -10008,13 +10038,14 @@ var require_client = __commonJS({
10008
10038
  let emittedAnythingAcrossAttempts = false;
10009
10039
  let emittedCharsAcrossAttempts = 0;
10010
10040
  const buildFetchArgs = () => {
10041
+ const signal = controller.signal;
10011
10042
  if (resuming) {
10012
10043
  return [
10013
10044
  `${exports.API_BASE}/api/code/chat/resume?turnId=${encodeURIComponent(turnId)}`,
10014
10045
  {
10015
10046
  method: "GET",
10016
10047
  headers: { ...authHeaders(), "Last-Event-ID": String(lastEventId) },
10017
- signal: controller.signal
10048
+ signal
10018
10049
  }
10019
10050
  ];
10020
10051
  }
@@ -10023,8 +10054,8 @@ var require_client = __commonJS({
10023
10054
  {
10024
10055
  method: "POST",
10025
10056
  headers: authHeaders(),
10026
- body: JSON.stringify({ messages, model, env: env3, editorContext, nexrallMd, mode, effort, clientType, extraTools, agents, skills, turnId, canWriteSharedMemory, hasOwnAgentStore, canSpawnSubAgents }),
10027
- signal: controller.signal
10057
+ body: JSON.stringify({ messages, model, env: env3, editorContext, nexrallMd, mode, effort, clientType, extraTools, agents, skills, turnId, canWriteSharedMemory, hasOwnAgentStore, canSpawnSubAgents, canBrowseWeb }),
10058
+ signal
10028
10059
  }
10029
10060
  ];
10030
10061
  };
@@ -10085,7 +10116,7 @@ var require_client = __commonJS({
10085
10116
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
10086
10117
  try {
10087
10118
  errorBodyOverride = null;
10088
- response = await (0, node_fetch_1.default)(...buildFetchArgs());
10119
+ response = await fetchWithHeaderTimeout(...buildFetchArgs(), controller.signal);
10089
10120
  if (response.status === 429 && attempt < MAX_RETRIES && canRetry()) {
10090
10121
  reportRetry("Rate limited by the API \u2014 retrying");
10091
10122
  const retryAfter = parseInt(response.headers.get("retry-after") ?? "0", 10);
@@ -16248,6 +16279,10 @@ var require_loop = __commonJS({
16248
16279
  case "empty-response":
16249
16280
  return `
16250
16281
  \u26A0\uFE0F The model returned an empty response, so nothing was done. This is usually a transient upstream hiccup \u2014 send "continue" to retry.
16282
+ `;
16283
+ case "output-limit":
16284
+ return `
16285
+ \u26A0\uFE0F The model used its entire output budget on internal reasoning and was cut off before it could reply, so nothing was done. This will repeat identically if you just retry \u2014 lower the reasoning effort (/effort) or split the task into smaller steps.
16251
16286
  `;
16252
16287
  case "no-balance":
16253
16288
  return `
@@ -17140,6 +17175,12 @@ ${tail}`;
17140
17175
  return "";
17141
17176
  return first.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("\n").trim();
17142
17177
  }
17178
+ var CompactionUnavailableError = class extends Error {
17179
+ constructor() {
17180
+ super("Compaction summariser was unavailable");
17181
+ this.name = "CompactionUnavailableError";
17182
+ }
17183
+ };
17143
17184
  async function autoCompactMessages(messages, options, ledger) {
17144
17185
  const cut = findSafeCutIndex(messages, messages.length - COMPACT_KEEP_MIN);
17145
17186
  if (cut < 2)
@@ -17178,7 +17219,7 @@ ${tail}`;
17178
17219
  });
17179
17220
  summary = reply.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("").trim();
17180
17221
  } catch {
17181
- return false;
17222
+ throw new CompactionUnavailableError();
17182
17223
  }
17183
17224
  if (!summary)
17184
17225
  return false;
@@ -17253,21 +17294,28 @@ Continue the work from here.` }] });
17253
17294
  let guard = 0;
17254
17295
  while (overCompactThreshold() && messages.length > COMPACT_KEEP_MIN + 2 && guard < 5) {
17255
17296
  guard += 1;
17256
- const did = await autoCompactMessages(messages, {
17257
- workDir: opts.workDir,
17258
- model: opts.model,
17259
- clientType: opts.clientType,
17260
- env: opts.env,
17261
- onText: () => {
17262
- },
17263
- onToolUse: () => {
17264
- },
17265
- onToolResult: () => {
17266
- },
17267
- onUsage: () => {
17268
- },
17269
- requestPermission: async () => false
17270
- });
17297
+ let did;
17298
+ try {
17299
+ did = await autoCompactMessages(messages, {
17300
+ workDir: opts.workDir,
17301
+ model: opts.model,
17302
+ clientType: opts.clientType,
17303
+ env: opts.env,
17304
+ onText: () => {
17305
+ },
17306
+ onToolUse: () => {
17307
+ },
17308
+ onToolResult: () => {
17309
+ },
17310
+ onUsage: () => {
17311
+ },
17312
+ requestPermission: async () => false
17313
+ });
17314
+ } catch (err) {
17315
+ if (err instanceof CompactionUnavailableError)
17316
+ break;
17317
+ throw err;
17318
+ }
17271
17319
  if (!did)
17272
17320
  break;
17273
17321
  compacted = true;
@@ -17324,6 +17372,8 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
17324
17372
  const ledger = createLedger();
17325
17373
  let compactFailures = 0;
17326
17374
  let compactDisabled = false;
17375
+ let compactDisabledAtBytes = 0;
17376
+ const COMPACT_REARM_GROWTH = 1.5;
17327
17377
  try {
17328
17378
  for (; iteration < budget; iteration++) {
17329
17379
  if (options.abortSignal?.aborted) {
@@ -17342,11 +17392,23 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
17342
17392
  (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.`);
17343
17393
  }
17344
17394
  }
17395
+ if (compactDisabled && bodyBytes > compactDisabledAtBytes * COMPACT_REARM_GROWTH) {
17396
+ compactDisabled = false;
17397
+ compactFailures = 0;
17398
+ }
17345
17399
  if (autoCompact && !compactDisabled && !compacting && (tokenPressure || bytePressure) && messages.length > COMPACT_KEEP_MIN + 2) {
17346
17400
  compacting = true;
17347
17401
  try {
17348
17402
  const bytesBefore = estimateBodyBytes2(messages);
17349
- const did = await autoCompactMessages(messages, options, ledger);
17403
+ let did = false;
17404
+ let unavailable = false;
17405
+ try {
17406
+ did = await autoCompactMessages(messages, options, ledger);
17407
+ } catch (err) {
17408
+ if (!(err instanceof CompactionUnavailableError))
17409
+ throw err;
17410
+ unavailable = true;
17411
+ }
17350
17412
  const bytesAfter = did ? estimateBodyBytes2(messages) : bytesBefore;
17351
17413
  const reclaimed = bytesBefore - bytesAfter;
17352
17414
  if (did) {
@@ -17356,11 +17418,13 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
17356
17418
  bodyBytes = bytesAfter;
17357
17419
  bytePressure = bodyBytes > MAX_BODY_BYTES;
17358
17420
  }
17359
- if (did && reclaimed >= COMPACT_MIN_RECLAIM_BYTES) {
17421
+ if (unavailable) {
17422
+ } else if (did && reclaimed >= COMPACT_MIN_RECLAIM_BYTES) {
17360
17423
  compactFailures = 0;
17361
17424
  } else if (++compactFailures >= COMPACT_MAX_FAILURES) {
17362
17425
  compactDisabled = true;
17363
- (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.`);
17426
+ compactDisabledAtBytes = bytesAfter;
17427
+ (options.onNotice ?? options.onText)(`\u26A0\uFE0F Auto-compaction isn't reducing this conversation any further, so it's paused to avoid repeated summarising. It will be retried automatically if the conversation grows substantially. If the context fills up before then, start a fresh chat or run /compact manually.`);
17364
17428
  }
17365
17429
  } finally {
17366
17430
  compacting = false;
@@ -17441,6 +17505,16 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
17441
17505
  canWriteSharedMemory: options._allowedTools ? options._allowedTools.has("memory_write") : true,
17442
17506
  // no allowlist = main agent = may write
17443
17507
  hasOwnAgentStore: !!options._agentMemory,
17508
+ // Same derivation, same reason, for the network. The base prompt's
17509
+ // "Web search" section tells the run to reach the network, but
17510
+ // READ_ONLY_TOOLS (explorer, security-auditor, ...) contains neither
17511
+ // `fetch_url` nor `web_search`. MEASURED on the 195-trial 2026-08-10
17512
+ // deepseek-v4-pro benchmark: 29 of 51 `fetch_url` failures and 3 of 14
17513
+ // `web_search` failures were the permission gate refusing a sub-agent
17514
+ // that its own system prompt had just invited to browse — every one a
17515
+ // wasted round-trip on a paid turn.
17516
+ canBrowseWeb: options._allowedTools ? options._allowedTools.has("fetch_url") : true,
17517
+ // no allowlist = main agent = may browse
17444
17518
  // Withholds the `task` schema and its instructions when this run cannot
17445
17519
  // delegate — see canSpawnSubAgents.
17446
17520
  canSpawnSubAgents: maySpawn,
@@ -17481,7 +17555,7 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
17481
17555
  continue;
17482
17556
  }
17483
17557
  runSimpleHooks(hooks.PostMessageComplete, options.workDir);
17484
- stopReason = "empty-response";
17558
+ stopReason = assistantMessage.stopReason === "max_tokens" ? "output-limit" : "empty-response";
17485
17559
  break;
17486
17560
  }
17487
17561
  const { stopReason: _stopReason, ...historyMessage } = assistantMessage;
@@ -53543,7 +53617,7 @@ var {
53543
53617
 
53544
53618
  // src/index.ts
53545
53619
  import * as path5 from "path";
53546
- import { createRequire } from "module";
53620
+ import { createRequire as createRequire2 } from "module";
53547
53621
 
53548
53622
  // ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
53549
53623
  var ANSI_BACKGROUND_OFFSET = 10;
@@ -54171,6 +54245,8 @@ import * as path4 from "path";
54171
54245
  import * as fs8 from "fs";
54172
54246
  import * as os4 from "os";
54173
54247
  import * as crypto from "crypto";
54248
+ import { createRequire } from "module";
54249
+ import { fileURLToPath as fileURLToPath2 } from "url";
54174
54250
  import { execSync } from "child_process";
54175
54251
  var import_code_core3 = __toESM(require_dist2(), 1);
54176
54252
 
@@ -64732,7 +64808,26 @@ var InkReadlineAdapter = class extends EventEmitter3 {
64732
64808
  };
64733
64809
 
64734
64810
  // src/commands/chat.ts
64735
- var CLI_VERSION = "0.5.63";
64811
+ var require2 = createRequire(import.meta.url);
64812
+ var CLI_VERSION = (() => {
64813
+ let dir = path4.dirname(fileURLToPath2(import.meta.url));
64814
+ for (let i2 = 0; i2 < 6; i2++) {
64815
+ const candidate = path4.join(dir, "package.json");
64816
+ if (fs8.existsSync(candidate)) {
64817
+ try {
64818
+ const v = require2(candidate).version;
64819
+ if (typeof v === "string" && v)
64820
+ return v;
64821
+ } catch {
64822
+ }
64823
+ }
64824
+ const parent = path4.dirname(dir);
64825
+ if (parent === dir)
64826
+ break;
64827
+ dir = parent;
64828
+ }
64829
+ return "unknown";
64830
+ })();
64736
64831
  var MODEL_LABELS = {
64737
64832
  "claude-sonnet-5": "Claude Sonnet 5",
64738
64833
  "claude-opus-5": "Claude Opus 5",
@@ -66481,8 +66576,8 @@ function pluginSourceRemoveCommand(name, opts) {
66481
66576
  }
66482
66577
 
66483
66578
  // src/index.ts
66484
- var require2 = createRequire(import.meta.url);
66485
- var NEX_VERSION = require2("../package.json").version;
66579
+ var require3 = createRequire2(import.meta.url);
66580
+ var NEX_VERSION = require3("../package.json").version;
66486
66581
  var program2 = new Command();
66487
66582
  program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version(NEX_VERSION).enablePositionalOptions();
66488
66583
  program2.command("auth").description("Login to your Nexrall account").action(authCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexrall-code",
3
- "version": "0.5.70",
3
+ "version": "0.5.72",
4
4
  "description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
5
5
  "keywords": [
6
6
  "ai",
@@ -39,7 +39,7 @@
39
39
  "release": "node build.js && node scripts/upload-release.cjs"
40
40
  },
41
41
  "dependencies": {
42
- "@nexrall/code-core": "^1.4.39",
42
+ "@nexrall/code-core": "^1.4.42",
43
43
  "chalk": "^5.3.0",
44
44
  "commander": "^12.0.0",
45
45
  "diff": "^5.2.0",