nexrall-code 0.5.23 → 0.5.24

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 +228 -27
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -12800,7 +12800,7 @@ ${fresh || "(no new output)"}${trunc}` };
12800
12800
  }
12801
12801
  return { output: `Background shell ${id} ${shell.status}.` };
12802
12802
  }
12803
- async function bash(input, abortSignal, sandbox, workDir) {
12803
+ async function bash(input, abortSignal, sandbox, workDir, onStream) {
12804
12804
  const command = typeof input.command === "string" ? input.command : "";
12805
12805
  const _rawDesc = typeof input.description === "string" ? input.description : "";
12806
12806
  void _rawDesc.slice(0, 200);
@@ -12951,8 +12951,22 @@ ${body}` : void 0 }));
12951
12951
  resolve3(prependNote({ output: body, exitCode: 0 }));
12952
12952
  }
12953
12953
  };
12954
- child.stdout?.on("data", (chunk) => appendOutput(chunk.toString("utf-8")));
12955
- child.stderr?.on("data", (chunk) => appendOutput(chunk.toString("utf-8")));
12954
+ child.stdout?.on("data", (chunk) => {
12955
+ const text = chunk.toString("utf-8");
12956
+ appendOutput(text);
12957
+ try {
12958
+ onStream?.(text);
12959
+ } catch {
12960
+ }
12961
+ });
12962
+ child.stderr?.on("data", (chunk) => {
12963
+ const text = chunk.toString("utf-8");
12964
+ appendOutput(text);
12965
+ try {
12966
+ onStream?.(text);
12967
+ } catch {
12968
+ }
12969
+ });
12956
12970
  child.on("close", (code, signal) => done(code, signal));
12957
12971
  child.on("error", (err) => {
12958
12972
  settled = true;
@@ -14148,12 +14162,12 @@ ${expanded}` };
14148
14162
  "memory_read",
14149
14163
  "use_skill"
14150
14164
  ]);
14151
- async function executeTool(name, input, abortSignal, sandbox, workDir, agentScope) {
14165
+ async function executeTool(name, input, abortSignal, sandbox, workDir, agentScope, onStream) {
14152
14166
  if (!TOOL_MAP[name])
14153
14167
  return { error: `Unknown tool: ${name}` };
14154
14168
  try {
14155
14169
  if (name === "bash")
14156
- return await bash(input, abortSignal, sandbox, workDir);
14170
+ return await bash(input, abortSignal, sandbox, workDir, input.run_in_background === true ? void 0 : onStream);
14157
14171
  if (name === "todo_write")
14158
14172
  return await todoWrite(input, agentScope);
14159
14173
  if (name === "todo_read")
@@ -14218,7 +14232,7 @@ var require_agentTypes = __commonJS({
14218
14232
  };
14219
14233
  }();
14220
14234
  Object.defineProperty(exports2, "__esModule", { value: true });
14221
- exports2.loadAgentTypes = loadAgentTypes;
14235
+ exports2.loadAgentTypes = loadAgentTypes2;
14222
14236
  exports2.summariseAgents = summariseAgents;
14223
14237
  exports2.findAgentType = findAgentType;
14224
14238
  var fs6 = __importStar(require("fs"));
@@ -14297,7 +14311,7 @@ var require_agentTypes = __commonJS({
14297
14311
  }
14298
14312
  }
14299
14313
  }
14300
- function loadAgentTypes(workDir) {
14314
+ function loadAgentTypes2(workDir) {
14301
14315
  const out = /* @__PURE__ */ new Map();
14302
14316
  loadDir(path5.join(workDir, ".nexrall", "agents"), "project", out);
14303
14317
  loadDir(path5.join(os5.homedir(), ".nexrall", "agents"), "global", out);
@@ -14718,14 +14732,16 @@ var require_loop = __commonJS({
14718
14732
  Object.defineProperty(exports2, "__esModule", { value: true });
14719
14733
  exports2.VERIFY_CMD_RE = exports2.WRITE_TOOL_NAMES = void 0;
14720
14734
  exports2.resolveMaxIterations = resolveMaxIterations;
14721
- exports2.estimateBodyBytes = estimateBodyBytes;
14735
+ exports2.contextWindowFor = contextWindowFor2;
14736
+ exports2.compactionThresholds = compactionThresholds2;
14737
+ exports2.estimateBodyBytes = estimateBodyBytes2;
14722
14738
  exports2.findSafeCutIndex = findSafeCutIndex;
14723
14739
  exports2.transcriptOf = transcriptOf;
14724
14740
  exports2.createLedger = createLedger;
14725
14741
  exports2.ledgerRecord = ledgerRecord;
14726
14742
  exports2.ledgerSummary = ledgerSummary;
14727
14743
  exports2.pruneOldToolResults = pruneOldToolResults;
14728
- exports2.estimateTokensRough = estimateTokensRough;
14744
+ exports2.estimateTokensRough = estimateTokensRough2;
14729
14745
  exports2.compactMessagesForResume = compactMessagesForResume2;
14730
14746
  exports2.runAgentLoop = runAgentLoop2;
14731
14747
  exports2.trimToResumableBoundary = trimToResumableBoundary;
@@ -14961,6 +14977,7 @@ var require_loop = __commonJS({
14961
14977
  }
14962
14978
  var MAX_TASK_DEPTH = 2;
14963
14979
  var _subTaskCounter = 0;
14980
+ var SUBTASK_TIMEOUT_MS = Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS) > 0 ? Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS) : 10 * 60 * 1e3;
14964
14981
  async function runSubTask(input, options, agentTypes) {
14965
14982
  const prompt2 = typeof input.prompt === "string" ? input.prompt.trim() : "";
14966
14983
  if (!prompt2)
@@ -14990,6 +15007,14 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
14990
15007
  const subMessages = [
14991
15008
  { role: "user", content: [{ type: "text", text: prompt2 }] }
14992
15009
  ];
15010
+ const subAbort = { aborted: false };
15011
+ const timer = setTimeout(() => {
15012
+ subAbort.aborted = true;
15013
+ }, SUBTASK_TIMEOUT_MS);
15014
+ const parentAbortPoll = setInterval(() => {
15015
+ if (options.abortSignal?.aborted)
15016
+ subAbort.aborted = true;
15017
+ }, 250);
14993
15018
  try {
14994
15019
  const result = await runAgentLoop2(subMessages, {
14995
15020
  ...options,
@@ -15000,6 +15025,7 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
15000
15025
  // fresh isolated context for sub-agent
15001
15026
  model: agent?.model ?? options.model,
15002
15027
  nexrallMd: subNexrallMd,
15028
+ abortSignal: subAbort,
15003
15029
  requestPermission: gatedPermission,
15004
15030
  onText: () => {
15005
15031
  },
@@ -15025,11 +15051,15 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
15025
15051
  // when the name was already labelled, and mixed display concerns into the data layer).
15026
15052
  onToolUse: (n, i2) => options.onToolUse(n, i2, true),
15027
15053
  onToolResult: (n, r2) => options.onToolResult(n, r2, true),
15054
+ onToolStreamChunk: (n, c) => options.onToolStreamChunk?.(n, c, true),
15028
15055
  // Forward thinking so the UI shows the indicator while sub-agent reasons
15029
15056
  onThinking: (text2) => options.onThinking?.(text2),
15030
15057
  onThinkingDelta: (text2) => options.onThinkingDelta?.(text2),
15031
15058
  onThinkingProgress: (tok) => options.onThinkingProgress?.(tok)
15032
15059
  });
15060
+ if (subAbort.aborted && !options.abortSignal?.aborted) {
15061
+ return { error: `Sub-task stalled and was stopped after ${Math.round(SUBTASK_TIMEOUT_MS / 6e4)} minutes with no completion. Consider breaking it into smaller sub-tasks or investigating what it may have been stuck on (a hung command, an unresponsive MCP/tool call, or a very large scope).` };
15062
+ }
15033
15063
  const lastAssistant = [...result].reverse().find((m2) => m2.role === "assistant");
15034
15064
  let text = (lastAssistant?.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join("").trim();
15035
15065
  const SUBTASK_MAX = 48e3;
@@ -15045,6 +15075,9 @@ ${tail}`;
15045
15075
  return { output: text || "(sub-task completed with no text output)" };
15046
15076
  } catch (err) {
15047
15077
  return { error: `Sub-task failed: ${err.message}` };
15078
+ } finally {
15079
+ clearTimeout(timer);
15080
+ clearInterval(parentAbortPoll);
15048
15081
  }
15049
15082
  }
15050
15083
  var MODEL_CONTEXT_TOKENS = {
@@ -15052,16 +15085,22 @@ ${tail}`;
15052
15085
  pro: 1e6,
15053
15086
  ultra: 1e6
15054
15087
  };
15088
+ function contextWindowFor2(model) {
15089
+ return MODEL_CONTEXT_TOKENS[model ?? "turbo"] ?? 1e6;
15090
+ }
15055
15091
  function envFraction(name, fallback) {
15056
15092
  const v = Number(process.env[name]);
15057
15093
  return Number.isFinite(v) && v > 0 && v < 1 ? v : fallback;
15058
15094
  }
15059
15095
  var AUTO_PRUNE_THRESHOLD = envFraction("NEXRALL_PRUNE_THRESHOLD", 0.35);
15060
15096
  var AUTO_COMPACT_THRESHOLD = envFraction("NEXRALL_COMPACT_THRESHOLD", 0.8);
15097
+ function compactionThresholds2() {
15098
+ return { prune: AUTO_PRUNE_THRESHOLD, compact: AUTO_COMPACT_THRESHOLD };
15099
+ }
15061
15100
  var PRUNE_MIN_RECLAIM_BYTES = 256 * 1024;
15062
15101
  var COMPACT_KEEP_MIN = 6;
15063
15102
  var MAX_BODY_BYTES = 8 * 1024 * 1024;
15064
- function estimateBodyBytes(messages) {
15103
+ function estimateBodyBytes2(messages) {
15065
15104
  try {
15066
15105
  return Buffer.byteLength(JSON.stringify(messages), "utf-8");
15067
15106
  } catch {
@@ -15327,8 +15366,8 @@ Continue the work from here.` }] });
15327
15366
  }
15328
15367
  }
15329
15368
  var RESUME_CHARS_PER_TOKEN = 4;
15330
- function estimateTokensRough(messages) {
15331
- return Math.ceil(estimateBodyBytes(messages) / RESUME_CHARS_PER_TOKEN);
15369
+ function estimateTokensRough2(messages) {
15370
+ return Math.ceil(estimateBodyBytes2(messages) / RESUME_CHARS_PER_TOKEN);
15332
15371
  }
15333
15372
  async function compactMessagesForResume2(messages, opts) {
15334
15373
  if (messages.length <= COMPACT_KEEP_MIN + 2)
@@ -15337,8 +15376,8 @@ Continue the work from here.` }] });
15337
15376
  if (!resolveAutoCompact(void 0, settings.raw))
15338
15377
  return false;
15339
15378
  const contextWindow = MODEL_CONTEXT_TOKENS[opts.model ?? "turbo"] ?? 1e6;
15340
- let bodyBytes = estimateBodyBytes(messages);
15341
- let tokenGuess = estimateTokensRough(messages);
15379
+ let bodyBytes = estimateBodyBytes2(messages);
15380
+ let tokenGuess = estimateTokensRough2(messages);
15342
15381
  const overPruneThreshold = () => tokenGuess > contextWindow * AUTO_PRUNE_THRESHOLD || bodyBytes > MAX_BODY_BYTES;
15343
15382
  const overCompactThreshold = () => tokenGuess > contextWindow * AUTO_COMPACT_THRESHOLD || bodyBytes > MAX_BODY_BYTES;
15344
15383
  if (!overPruneThreshold())
@@ -15347,8 +15386,8 @@ Continue the work from here.` }] });
15347
15386
  if (messages.length > PRUNE_KEEP_RECENT + 2) {
15348
15387
  const reclaimed = pruneOldToolResults(messages, PRUNE_MIN_RECLAIM_BYTES);
15349
15388
  if (reclaimed > 0) {
15350
- bodyBytes = estimateBodyBytes(messages);
15351
- tokenGuess = estimateTokensRough(messages);
15389
+ bodyBytes = estimateBodyBytes2(messages);
15390
+ tokenGuess = estimateTokensRough2(messages);
15352
15391
  compacted = true;
15353
15392
  opts.onNotice?.(`
15354
15393
  \u267B\uFE0F Trimmed ~${(reclaimed / (1024 * 1024)).toFixed(1)}MB of older tool output before resuming this chat.
@@ -15376,8 +15415,8 @@ Continue the work from here.` }] });
15376
15415
  if (!did)
15377
15416
  break;
15378
15417
  compacted = true;
15379
- bodyBytes = estimateBodyBytes(messages);
15380
- tokenGuess = estimateTokensRough(messages);
15418
+ bodyBytes = estimateBodyBytes2(messages);
15419
+ tokenGuess = estimateTokensRough2(messages);
15381
15420
  }
15382
15421
  if (compacted) {
15383
15422
  opts.onNotice?.(`
@@ -15422,14 +15461,14 @@ Continue the work from here.` }] });
15422
15461
  for (; iteration < budget; iteration++) {
15423
15462
  if (options.abortSignal?.aborted)
15424
15463
  break;
15425
- let bodyBytes = estimateBodyBytes(messages);
15464
+ let bodyBytes = estimateBodyBytes2(messages);
15426
15465
  const prunePressure = lastPromptTokens > contextWindow * AUTO_PRUNE_THRESHOLD;
15427
15466
  const tokenPressure = lastPromptTokens > contextWindow * AUTO_COMPACT_THRESHOLD;
15428
15467
  let bytePressure = bodyBytes > MAX_BODY_BYTES;
15429
15468
  if (autoCompact && !compacting && (prunePressure || bytePressure) && messages.length > PRUNE_KEEP_RECENT + 2) {
15430
15469
  const reclaimed = pruneOldToolResults(messages, PRUNE_MIN_RECLAIM_BYTES);
15431
15470
  if (reclaimed > 0) {
15432
- bodyBytes = estimateBodyBytes(messages);
15471
+ bodyBytes = estimateBodyBytes2(messages);
15433
15472
  bytePressure = bodyBytes > MAX_BODY_BYTES;
15434
15473
  (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.`);
15435
15474
  }
@@ -15649,12 +15688,13 @@ Continue the work from here.` }] });
15649
15688
  } else {
15650
15689
  options.checkpointManager?.recordBeforeMutation(name, input);
15651
15690
  const WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "edit_file", "multi_edit", "delete_file", "move_file", "copy_file", "notebook_edit"]);
15691
+ const onStream = options.onToolStreamChunk ? (chunk) => options.onToolStreamChunk(name, chunk) : void 0;
15652
15692
  if (WRITE_TOOLS.has(name)) {
15653
15693
  const targetPath = typeof input.path === "string" ? input.path : typeof input.source === "string" ? input.source : "";
15654
15694
  const absTarget = targetPath && options.workDir ? path5.resolve(options.workDir, targetPath) : targetPath;
15655
- result = await withFileLock(absTarget || name, () => (0, executor_1.executeTool)(name, input, options.abortSignal, sandboxCfg, options.workDir, agentScope));
15695
+ result = await withFileLock(absTarget || name, () => (0, executor_1.executeTool)(name, input, options.abortSignal, sandboxCfg, options.workDir, agentScope, onStream));
15656
15696
  } else {
15657
- result = await (0, executor_1.executeTool)(name, input, options.abortSignal, sandboxCfg, options.workDir, agentScope);
15697
+ result = await (0, executor_1.executeTool)(name, input, options.abortSignal, sandboxCfg, options.workDir, agentScope, onStream);
15658
15698
  }
15659
15699
  }
15660
15700
  } catch (err) {
@@ -16323,7 +16363,7 @@ var require_manager = __commonJS({
16323
16363
  var httpClient_1 = require_httpClient();
16324
16364
  var sseClient_1 = require_sseClient();
16325
16365
  var index_1 = require_plugins();
16326
- var McpManager = class _McpManager {
16366
+ var McpManager2 = class _McpManager {
16327
16367
  constructor() {
16328
16368
  this._clients = /* @__PURE__ */ new Map();
16329
16369
  this._toolMap = /* @__PURE__ */ new Map();
@@ -16496,7 +16536,7 @@ var require_manager = __commonJS({
16496
16536
  this._needsAuth.clear();
16497
16537
  }
16498
16538
  };
16499
- exports2.McpManager = McpManager;
16539
+ exports2.McpManager = McpManager2;
16500
16540
  }
16501
16541
  });
16502
16542
 
@@ -23365,6 +23405,49 @@ function formatToolUse(name, input) {
23365
23405
  const argDisplay = primaryArg ? source_default.dim(" ") + source_default.white(String(primaryArg).replace(/\n/g, "\u21B5").slice(0, 80)) : "";
23366
23406
  return source_default.yellow(icons.tool) + " " + source_default.bold(name) + argDisplay;
23367
23407
  }
23408
+ var ToolStreamPrinter = class _ToolStreamPrinter {
23409
+ carry = "";
23410
+ linesShown = 0;
23411
+ // Cap visible lines per command so a runaway/very chatty process (e.g. a
23412
+ // build tool re-printing a progress bar thousands of times) doesn't flood
23413
+ // the terminal — the full output is still available afterward via
23414
+ // formatToolResult's summary and, for large output, the spill file.
23415
+ static MAX_LINES = 200;
23416
+ capped = false;
23417
+ feed(chunk) {
23418
+ if (this.capped)
23419
+ return;
23420
+ this.carry += chunk;
23421
+ const lines = this.carry.split("\n");
23422
+ this.carry = lines.pop() ?? "";
23423
+ for (const line of lines) {
23424
+ if (!this.printLine(line))
23425
+ return;
23426
+ }
23427
+ }
23428
+ printLine(line) {
23429
+ if (this.linesShown >= _ToolStreamPrinter.MAX_LINES) {
23430
+ if (!this.capped) {
23431
+ this.capped = true;
23432
+ process.stdout.write(source_default.dim(" \u2502 \u2026 output continues (full log in the result above once finished)\n"));
23433
+ }
23434
+ return false;
23435
+ }
23436
+ this.linesShown++;
23437
+ process.stdout.write(source_default.dim(" \u2502 ") + source_default.gray(line.slice(0, 300)) + "\n");
23438
+ return true;
23439
+ }
23440
+ flush() {
23441
+ if (!this.capped && this.carry)
23442
+ this.printLine(this.carry);
23443
+ this.carry = "";
23444
+ }
23445
+ reset() {
23446
+ this.carry = "";
23447
+ this.linesShown = 0;
23448
+ this.capped = false;
23449
+ }
23450
+ };
23368
23451
  function formatToolResult(name, result, durationMs) {
23369
23452
  if (result.error) {
23370
23453
  return source_default.red(icons.cross) + " " + source_default.dim(name) + source_default.dim(" \xB7 ") + source_default.red("error") + source_default.dim(` \xB7 ${durationMs}ms`);
@@ -24500,10 +24583,71 @@ var Spinner = class {
24500
24583
  return this.interval !== null;
24501
24584
  }
24502
24585
  };
24586
+ var _mcpManager = null;
24587
+ var _mcpConfig = null;
24588
+ async function initMcp(workDir) {
24589
+ _mcpConfig = import_code_core3.McpManager.loadConfig(workDir);
24590
+ if (Object.keys(_mcpConfig.mcpServers).length === 0) {
24591
+ _mcpManager = null;
24592
+ return;
24593
+ }
24594
+ const manager = new import_code_core3.McpManager();
24595
+ await manager.connectAll(_mcpConfig);
24596
+ _mcpManager = manager;
24597
+ }
24598
+ function formatMcpStatus() {
24599
+ if (!_mcpConfig || Object.keys(_mcpConfig.mcpServers).length === 0) {
24600
+ return source_default.dim(" No MCP servers configured. Add one to .nexrall/mcp.json or ~/.nexrall/mcp.json.");
24601
+ }
24602
+ if (!_mcpManager)
24603
+ return source_default.dim(" MCP servers configured but not connected.");
24604
+ const rows = _mcpManager.getStatus();
24605
+ const lines = rows.map((s2) => {
24606
+ if (s2.connected) {
24607
+ return " " + source_default.green("\u25CF") + " " + source_default.bold(s2.name) + source_default.dim(` (${s2.transport}) \u2014 ${s2.tools.length} tool(s)`);
24608
+ }
24609
+ const authNote = s2.needsAuth ? source_default.yellow(" [needs OAuth \u2014 not supported in CLI yet, add a static Authorization header]") : "";
24610
+ return " " + source_default.red("\u25CF") + " " + source_default.bold(s2.name) + source_default.dim(` (${s2.transport}) \u2014 `) + source_default.red(s2.error ?? "failed to connect") + authNote;
24611
+ });
24612
+ return lines.join("\n");
24613
+ }
24614
+ function formatContextUsage(messages, modelAlias) {
24615
+ const window = (0, import_code_core3.contextWindowFor)(modelAlias);
24616
+ const tokens = (0, import_code_core3.estimateTokensRough)(messages);
24617
+ const bytes = (0, import_code_core3.estimateBodyBytes)(messages);
24618
+ const { prune, compact } = (0, import_code_core3.compactionThresholds)();
24619
+ const pct = Math.min(100, tokens / window * 100);
24620
+ const barWidth = 30;
24621
+ const filled = Math.round(pct / 100 * barWidth);
24622
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(barWidth - filled);
24623
+ const barColor = pct >= compact * 100 ? source_default.red : pct >= prune * 100 ? source_default.yellow : source_default.green;
24624
+ const lines = [
24625
+ "",
24626
+ " " + source_default.bold("Context usage"),
24627
+ " " + barColor(bar) + source_default.dim(` ~${pct.toFixed(1)}%`),
24628
+ source_default.dim(` ~${tokens.toLocaleString()} / ${window.toLocaleString()} tokens (rough estimate) \xB7 ${(bytes / 1024).toFixed(0)}KB serialized \xB7 ${messages.length} messages`),
24629
+ source_default.dim(` auto-prune at ~${(prune * 100).toFixed(0)}% \xB7 auto-compact (summarise) at ~${(compact * 100).toFixed(0)}%`),
24630
+ ""
24631
+ ];
24632
+ return lines.join("\n");
24633
+ }
24634
+ function formatAgentsList(workDir) {
24635
+ const types3 = (0, import_code_core3.loadAgentTypes)(workDir);
24636
+ if (!types3.length)
24637
+ return source_default.dim(" No agent types found (this should not happen \u2014 builtins always register).");
24638
+ const lines = types3.map((t2) => {
24639
+ const tools = t2.tools ? source_default.dim(` \u2014 tools: ${t2.tools.join(", ")}`) : source_default.dim(" \u2014 all tools");
24640
+ const model = t2.model ? source_default.dim(` \u2014 model: ${t2.model}`) : "";
24641
+ const scope = source_default.dim(` (${t2.source})`);
24642
+ return " " + source_default.cyan(t2.name.padEnd(16)) + source_default.white(t2.description) + scope + tools + model;
24643
+ });
24644
+ return lines.join("\n");
24645
+ }
24503
24646
  async function runTurn(messages, modelAlias, workDir, abortSignal, env2, nexrallMd, mode, effort, checkpointManager, onProgress) {
24504
24647
  let lastUsage;
24505
24648
  const spinner = new Spinner();
24506
24649
  const mdRender = new MarkdownStreamRenderer();
24650
+ const toolStream = new ToolStreamPrinter();
24507
24651
  let toolStartTime = 0;
24508
24652
  let lastToolName = "";
24509
24653
  let thinkingTokens = 0;
@@ -24551,14 +24695,27 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env2, nexrall
24551
24695
  return;
24552
24696
  mdRender.flush();
24553
24697
  spinner.stop();
24698
+ toolStream.reset();
24554
24699
  process.stdout.write("\n");
24555
24700
  console.log(formatToolUse(name, input));
24556
24701
  lastToolName = name;
24557
24702
  toolStartTime = Date.now();
24558
24703
  spinner.start(`running ${name}\u2026`);
24559
24704
  },
24705
+ // Live progress for foreground bash: the FIRST chunk stops the "running…"
24706
+ // spinner (which would otherwise overwrite/interleave badly with printed
24707
+ // lines) and every subsequent chunk streams straight to the terminal —
24708
+ // see ToolStreamPrinter for the line-buffering/cap logic.
24709
+ onToolStreamChunk: (_name, chunk) => {
24710
+ if (abortSignal.aborted)
24711
+ return;
24712
+ if (spinner.active)
24713
+ spinner.stop();
24714
+ toolStream.feed(chunk);
24715
+ },
24560
24716
  onToolResult: (_name, res) => {
24561
24717
  spinner.stop();
24718
+ toolStream.flush();
24562
24719
  const durationMs = Date.now() - toolStartTime;
24563
24720
  console.log(formatToolResult(lastToolName, res, durationMs));
24564
24721
  toolStartTime = 0;
@@ -24610,7 +24767,8 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env2, nexrall
24610
24767
  },
24611
24768
  requestPermission,
24612
24769
  checkpointManager,
24613
- onProgress
24770
+ onProgress,
24771
+ mcpManager: _mcpManager ?? void 0
24614
24772
  });
24615
24773
  mdRender.flush();
24616
24774
  spinner.stop();
@@ -24650,6 +24808,13 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env2
24650
24808
  onToolUse: (name, input) => {
24651
24809
  emit({ type: "tool_use", tool: name, input });
24652
24810
  },
24811
+ // Live bash progress for scripted/CI consumers — lets a wrapper tail a
24812
+ // long-running build/test command instead of blocking silently until
24813
+ // tool_result. Chunk is raw and un-truncated (unlike the final result,
24814
+ // which is head/tail-capped), so a chatty command can emit many of these.
24815
+ onToolStreamChunk: (name, chunk) => {
24816
+ emit({ type: "tool_stream", tool: name, chunk });
24817
+ },
24653
24818
  onToolResult: (name, res) => {
24654
24819
  toolCallCount++;
24655
24820
  emit({ type: "tool_result", tool: name, ok: res.error === void 0, ...res.error ? { error: res.error } : {} });
@@ -24688,7 +24853,8 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env2
24688
24853
  return true;
24689
24854
  },
24690
24855
  checkpointManager,
24691
- onProgress
24856
+ onProgress,
24857
+ mcpManager: _mcpManager ?? void 0
24692
24858
  });
24693
24859
  if (format === "json") {
24694
24860
  process.stdout.write(JSON.stringify({
@@ -24725,6 +24891,9 @@ function printHelp() {
24725
24891
  ["/add <filepath>", "Add a file to conversation context"],
24726
24892
  ["/skills", "List skills (.nexrall/skills/<name>/SKILL.md or .nexrall/commands/*.md) \u2014 the model can also auto-invoke these"],
24727
24893
  ["/plugins", "List installed plugins (.nexrall/plugins/)"],
24894
+ ["/mcp", "Show MCP server connection status"],
24895
+ ["/agents", "List available sub-agent types (for the task tool)"],
24896
+ ["/context", "Show context window usage for this conversation"],
24728
24897
  ["/update", "Update nex to the latest version"]
24729
24898
  ];
24730
24899
  for (const [cmd, desc] of cmds) {
@@ -24785,6 +24954,15 @@ async function startChatSession(options) {
24785
24954
  let skills = (0, import_code_core3.loadSkills)(workDir);
24786
24955
  if (skills.length && !headless)
24787
24956
  console.log(source_default.dim(` ${skills.length} skill(s) loaded`));
24957
+ try {
24958
+ await initMcp(workDir);
24959
+ const connected = _mcpManager?.serverNames.length ?? 0;
24960
+ const total = _mcpConfig ? Object.keys(_mcpConfig.mcpServers).length : 0;
24961
+ if (total && !headless) {
24962
+ console.log(source_default.dim(` ${connected}/${total} MCP server(s) connected`) + source_default.dim(" (/mcp for details)"));
24963
+ }
24964
+ } catch {
24965
+ }
24788
24966
  if (headless && !options.prompt && !options.stdinText) {
24789
24967
  process.stdout.write(JSON.stringify({ type: "error", error: "--output-format json/stream-json requires a one-shot prompt (or piped stdin)." }) + "\n");
24790
24968
  process.exit(1);
@@ -25306,6 +25484,29 @@ ${content}
25306
25484
  rl.prompt();
25307
25485
  return;
25308
25486
  }
25487
+ case "/mcp": {
25488
+ console.log();
25489
+ console.log(source_default.bold(" MCP servers:"));
25490
+ console.log(formatMcpStatus());
25491
+ console.log();
25492
+ rl.prompt();
25493
+ return;
25494
+ }
25495
+ case "/agents": {
25496
+ console.log();
25497
+ console.log(source_default.bold(" Sub-agent types (used by the `task` tool):"));
25498
+ console.log(formatAgentsList(workDir));
25499
+ console.log();
25500
+ console.log(source_default.dim(" Define your own in .nexrall/agents/<name>.md or ~/.nexrall/agents/<name>.md"));
25501
+ console.log();
25502
+ rl.prompt();
25503
+ return;
25504
+ }
25505
+ case "/context": {
25506
+ console.log(formatContextUsage(messages, modelAlias));
25507
+ rl.prompt();
25508
+ return;
25509
+ }
25309
25510
  case "/skills":
25310
25511
  case "/commands": {
25311
25512
  skills = (0, import_code_core3.loadSkills)(workDir);
@@ -25596,7 +25797,7 @@ function pluginListCommand() {
25596
25797
 
25597
25798
  // src/index.ts
25598
25799
  var program2 = new Command();
25599
- program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.23");
25800
+ program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.24");
25600
25801
  program2.command("auth").description("Login to your Nexrall account").action(authCommand);
25601
25802
  program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
25602
25803
  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.23",
3
+ "version": "0.5.24",
4
4
  "description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
5
5
  "keywords": [
6
6
  "ai",
@@ -37,7 +37,7 @@
37
37
  "ora": "^8.0.1",
38
38
  "prompts": "^2.4.2",
39
39
  "readline": "^1.3.0",
40
- "@nexrall/code-core": "1.4.18"
40
+ "@nexrall/code-core": "1.4.19"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@aws-sdk/client-s3": "^3.600.0",