nexrall-code 0.5.69 → 0.5.71

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 +125 -30
  2. package/package.json +1 -1
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));
@@ -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
  }
@@ -10024,7 +10055,7 @@ var require_client = __commonJS({
10024
10055
  method: "POST",
10025
10056
  headers: authHeaders(),
10026
10057
  body: JSON.stringify({ messages, model, env: env3, editorContext, nexrallMd, mode, effort, clientType, extraTools, agents, skills, turnId, canWriteSharedMemory, hasOwnAgentStore, canSpawnSubAgents }),
10027
- signal: controller.signal
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;
@@ -17481,7 +17545,7 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
17481
17545
  continue;
17482
17546
  }
17483
17547
  runSimpleHooks(hooks.PostMessageComplete, options.workDir);
17484
- stopReason = "empty-response";
17548
+ stopReason = assistantMessage.stopReason === "max_tokens" ? "output-limit" : "empty-response";
17485
17549
  break;
17486
17550
  }
17487
17551
  const { stopReason: _stopReason, ...historyMessage } = assistantMessage;
@@ -53543,7 +53607,7 @@ var {
53543
53607
 
53544
53608
  // src/index.ts
53545
53609
  import * as path5 from "path";
53546
- import { createRequire } from "module";
53610
+ import { createRequire as createRequire2 } from "module";
53547
53611
 
53548
53612
  // ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
53549
53613
  var ANSI_BACKGROUND_OFFSET = 10;
@@ -54171,6 +54235,8 @@ import * as path4 from "path";
54171
54235
  import * as fs8 from "fs";
54172
54236
  import * as os4 from "os";
54173
54237
  import * as crypto from "crypto";
54238
+ import { createRequire } from "module";
54239
+ import { fileURLToPath as fileURLToPath2 } from "url";
54174
54240
  import { execSync } from "child_process";
54175
54241
  var import_code_core3 = __toESM(require_dist2(), 1);
54176
54242
 
@@ -64732,7 +64798,26 @@ var InkReadlineAdapter = class extends EventEmitter3 {
64732
64798
  };
64733
64799
 
64734
64800
  // src/commands/chat.ts
64735
- var CLI_VERSION = "0.5.63";
64801
+ var require2 = createRequire(import.meta.url);
64802
+ var CLI_VERSION = (() => {
64803
+ let dir = path4.dirname(fileURLToPath2(import.meta.url));
64804
+ for (let i2 = 0; i2 < 6; i2++) {
64805
+ const candidate = path4.join(dir, "package.json");
64806
+ if (fs8.existsSync(candidate)) {
64807
+ try {
64808
+ const v = require2(candidate).version;
64809
+ if (typeof v === "string" && v)
64810
+ return v;
64811
+ } catch {
64812
+ }
64813
+ }
64814
+ const parent = path4.dirname(dir);
64815
+ if (parent === dir)
64816
+ break;
64817
+ dir = parent;
64818
+ }
64819
+ return "unknown";
64820
+ })();
64736
64821
  var MODEL_LABELS = {
64737
64822
  "claude-sonnet-5": "Claude Sonnet 5",
64738
64823
  "claude-opus-5": "Claude Opus 5",
@@ -65408,7 +65493,8 @@ Set ${TRUST_ENV_VAR}=1 to confirm non-interactively, or run \`nex\` interactivel
65408
65493
  let sessionId = crypto.randomBytes(8).toString("hex");
65409
65494
  let sessionTitle = "";
65410
65495
  let agentMode = "auto";
65411
- let effortLevel = "medium";
65496
+ const VALID_EFFORT_LEVELS = ["low", "medium", "high", "extra"];
65497
+ let effortLevel = options.effort && VALID_EFFORT_LEVELS.includes(options.effort) ? options.effort : "medium";
65412
65498
  const permRules = initPermissions(workDir);
65413
65499
  const ruleCount = permRules.allow.length + permRules.ask.length + permRules.deny.length;
65414
65500
  if (ruleCount && !headless)
@@ -66480,8 +66566,8 @@ function pluginSourceRemoveCommand(name, opts) {
66480
66566
  }
66481
66567
 
66482
66568
  // src/index.ts
66483
- var require2 = createRequire(import.meta.url);
66484
- var NEX_VERSION = require2("../package.json").version;
66569
+ var require3 = createRequire2(import.meta.url);
66570
+ var NEX_VERSION = require3("../package.json").version;
66485
66571
  var program2 = new Command();
66486
66572
  program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version(NEX_VERSION).enablePositionalOptions();
66487
66573
  program2.command("auth").description("Login to your Nexrall account").action(authCommand);
@@ -66554,7 +66640,7 @@ program2.command("sessions").description("List or delete saved chat sessions").o
66554
66640
  console.log(source_default.dim(" nex sessions --delete <id> to remove one"));
66555
66641
  console.log();
66556
66642
  });
66557
- program2.option("-p, --pro", "Use Claude Opus 5 (most capable)").option("-t, --turbo", "Use Claude Sonnet 5 (fast, default)").option("-u, --ultra", "Use Claude Fable 5 (most powerful)").option("-y, --yolo", "Auto-approve all tool calls (no permission prompts)").option("-d, --dir <path>", "Set working directory (default: current directory)").option("-m, --model <name>", "Explicit model, e.g. claude-opus-5 | gpt-5.4 | gpt-4.1").option("-r, --resume [id]", "Resume last session, or a specific session id").option("--no-banner", "Skip the ASCII banner (useful in scripts/pipes)").option("--output-format <fmt>", "One-shot output format: text | json | stream-json (implies auto-approve)").argument("[prompt...]", "One-shot prompt \u2014 if omitted, starts interactive mode").action(async (promptParts, options) => {
66643
+ program2.option("-p, --pro", "Use Claude Opus 5 (most capable)").option("-t, --turbo", "Use Claude Sonnet 5 (fast, default)").option("-u, --ultra", "Use Claude Fable 5 (most powerful)").option("-y, --yolo", "Auto-approve all tool calls (no permission prompts)").option("-d, --dir <path>", "Set working directory (default: current directory)").option("-m, --model <name>", "Explicit model, e.g. claude-opus-5 | gpt-5.4 | gpt-4.1").option("-r, --resume [id]", "Resume last session, or a specific session id").option("-e, --effort <level>", "Thinking effort: low | medium | high | extra (default: medium)").option("--no-banner", "Skip the ASCII banner (useful in scripts/pipes)").option("--output-format <fmt>", "One-shot output format: text | json | stream-json (implies auto-approve)").argument("[prompt...]", "One-shot prompt \u2014 if omitted, starts interactive mode").action(async (promptParts, options) => {
66558
66644
  if (!(0, import_code_core6.isAuthenticated)()) {
66559
66645
  console.error("Not logged in. Run: nex auth");
66560
66646
  process.exit(1);
@@ -66586,7 +66672,16 @@ program2.option("-p, --pro", "Use Claude Opus 5 (most capable)").option("-t, --t
66586
66672
  }
66587
66673
  outputFormat = fmt;
66588
66674
  }
66589
- await startChatSession({ model, workDir, prompt: prompt2, resume, stdinText, showBanner, outputFormat });
66675
+ let effort;
66676
+ if (options.effort) {
66677
+ const lvl = String(options.effort).toLowerCase();
66678
+ if (!["low", "medium", "high", "extra"].includes(lvl)) {
66679
+ console.error(`Invalid --effort "${options.effort}". Use: low | medium | high | extra`);
66680
+ process.exit(1);
66681
+ }
66682
+ effort = lvl;
66683
+ }
66684
+ await startChatSession({ model, workDir, prompt: prompt2, resume, stdinText, showBanner, outputFormat, effort });
66590
66685
  });
66591
66686
  program2.parse();
66592
66687
  /*! Bundled license information:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexrall-code",
3
- "version": "0.5.69",
3
+ "version": "0.5.71",
4
4
  "description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
5
5
  "keywords": [
6
6
  "ai",