nexrall-code 0.5.54 → 0.5.56

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 +297 -50
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -14718,6 +14718,29 @@ var require_agentTypes = __commonJS({
14718
14718
  "notebook_edit"
14719
14719
  ];
14720
14720
  var BUILTIN_AGENTS = [
14721
+ {
14722
+ // The catch-all, matching Claude Code's `general-purpose`.
14723
+ //
14724
+ // This capability already existed — omitting `subagent_type` gives an unrestricted
14725
+ // sub-agent — but it had no NAME, and that had two consequences worth fixing:
14726
+ //
14727
+ // 1. `permissions.deny: ["task(...)"]` matches on the agent name, so the ONE
14728
+ // sub-agent that can write files and run bash was the one variant a project
14729
+ // could not disable individually. Only a blanket `deny: ["task"]` reached it.
14730
+ // 2. The model had to infer that leaving the field blank was even an option, so
14731
+ // it would sometimes pick a specialist that fitted badly (an unrestricted
14732
+ // explorer) rather than the general worker it actually wanted.
14733
+ //
14734
+ // `tools` is deliberately UNDEFINED, which means "no allowlist" — full access,
14735
+ // inheriting whatever the session permits. That is the same power an unnamed
14736
+ // sub-task always had; naming it changes only who can see and deny it. The safety
14737
+ // properties elsewhere still apply: plan mode is inherited, the user's permission
14738
+ // gate still runs on every call, and it cannot spawn further sub-agents.
14739
+ name: "general-purpose",
14740
+ description: "General-purpose worker for a multi-step task that needs BOTH exploration and changes (edit files, run commands) and that no specialist above fits. Inherits the session model and full tool access, so prefer a narrower agent when one matches.",
14741
+ prompt: "You are a general-purpose engineering sub-agent. Work the task end to end: explore what you need, make the changes, and verify them with the project's own build/test commands.\n\nRules:\n- Mirror existing conventions; make the smallest correct change.\n- Verify before you claim success. If you could not verify, say so explicitly.\n- Your FINAL MESSAGE is the only thing that reaches the main agent: state what you changed (with file paths), what you ran and its outcome, and anything you deliberately left undone.",
14742
+ source: "builtin"
14743
+ },
14721
14744
  {
14722
14745
  name: "reviewer",
14723
14746
  description: "Read-only code reviewer \u2014 finds correctness bugs, edge cases, and security issues in a diff or file set. Cannot modify files.",
@@ -14780,6 +14803,9 @@ var require_agentTypes = __commonJS({
14780
14803
  // frontier model, and this is the agent most likely to be spawned in bulk.
14781
14804
  {
14782
14805
  name: "explorer",
14806
+ // Lean prompt: this agent exists to keep bulk searching cheap, and it reports
14807
+ // findings for the MAIN agent to interpret with full project context.
14808
+ lightPrompt: true,
14783
14809
  description: "Fast read-only codebase explorer \u2014 locates files, symbols, and call sites and reports concise findings. Use to keep bulk searching out of the main context. Cannot modify files.",
14784
14810
  tools: READ_ONLY_TOOLS,
14785
14811
  model: "turbo",
@@ -15876,10 +15902,16 @@ var require_loop = __commonJS({
15876
15902
  exports.executeAgentMemoryWrite = executeAgentMemoryWrite;
15877
15903
  exports.stopReasonNotice = stopReasonNotice;
15878
15904
  exports.resolveMaxIterations = resolveMaxIterations;
15905
+ exports.bashNeedsRepoLock = bashNeedsRepoLock;
15906
+ exports.lockPathsFor = lockPathsFor;
15907
+ exports.resolveMaxConcurrentSubtasks = resolveMaxConcurrentSubtasks;
15879
15908
  exports.createLimiter = createLimiter;
15909
+ exports._resetSubTaskLimiter = _resetSubTaskLimiter;
15910
+ exports.resolveSubtaskTimeoutMs = resolveSubtaskTimeoutMs;
15880
15911
  exports.extractSubTaskText = extractSubTaskText;
15881
15912
  exports.capSubTaskText = capSubTaskText;
15882
15913
  exports.summariseSubTaskProgress = summariseSubTaskProgress;
15914
+ exports.lastToolResults = lastToolResults;
15883
15915
  exports.contextWindowFor = contextWindowFor2;
15884
15916
  exports.compactionThresholds = compactionThresholds2;
15885
15917
  exports.estimateBodyBytes = estimateBodyBytes2;
@@ -16116,10 +16148,89 @@ ${ctx.repeatError}
16116
16148
  _fileLocks.delete(absPath);
16117
16149
  }
16118
16150
  }
16119
- var MAX_CONCURRENT_SUBTASKS = (() => {
16120
- const raw = Number(process.env.NEXRALL_MAX_CONCURRENT_SUBTASKS);
16121
- return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 4;
16122
- })();
16151
+ async function withFileLocks(absPaths, fn) {
16152
+ const unique = [...new Set(absPaths.filter(Boolean))].sort();
16153
+ if (unique.length === 0)
16154
+ return fn();
16155
+ const [first, ...rest] = unique;
16156
+ return withFileLock(first, () => rest.length ? withFileLocks(rest, fn) : fn());
16157
+ }
16158
+ var WRITE_TOOLS = /* @__PURE__ */ new Set([
16159
+ "write_file",
16160
+ "edit_file",
16161
+ "multi_edit",
16162
+ "delete_file",
16163
+ "move_file",
16164
+ "copy_file",
16165
+ "notebook_edit"
16166
+ ]);
16167
+ var REPO_STATE_LOCK = "\0repo-state";
16168
+ var MUTATING_BASH_VERBS = /* @__PURE__ */ new Set([
16169
+ "git",
16170
+ "npm",
16171
+ "pnpm",
16172
+ "yarn",
16173
+ "bun",
16174
+ "cargo",
16175
+ "go",
16176
+ "pip",
16177
+ "pip3",
16178
+ "poetry",
16179
+ "uv",
16180
+ "bundle",
16181
+ "composer",
16182
+ "gradle",
16183
+ "mvn",
16184
+ "make",
16185
+ "terraform",
16186
+ "helm",
16187
+ "docker",
16188
+ "vsce",
16189
+ "tsc",
16190
+ "pytest",
16191
+ "dotnet",
16192
+ "swift",
16193
+ "gem"
16194
+ ]);
16195
+ function bashNeedsRepoLock(command) {
16196
+ if (!command)
16197
+ return false;
16198
+ const segments = command.split(/\|\||&&|[;|&\n()]|\$\(/);
16199
+ for (const seg of segments) {
16200
+ for (const tokenRaw of seg.trim().split(/\s+/)) {
16201
+ const token = tokenRaw.replace(/^["']|["']$/g, "");
16202
+ if (!token)
16203
+ continue;
16204
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(token))
16205
+ continue;
16206
+ if (token === "sudo" || token === "time" || token === "nice" || token === "env")
16207
+ continue;
16208
+ const verb = token.split("/").pop() ?? token;
16209
+ if (MUTATING_BASH_VERBS.has(verb))
16210
+ return true;
16211
+ break;
16212
+ }
16213
+ }
16214
+ return false;
16215
+ }
16216
+ function lockPathsFor(name, input, workDir) {
16217
+ if (!WRITE_TOOLS.has(name))
16218
+ return [];
16219
+ const raw = [input.path, input.source, input.destination, input.dest].filter((p) => typeof p === "string" && p.length > 0);
16220
+ const abs = raw.map((p) => workDir ? path6.resolve(workDir, p) : p);
16221
+ return abs.length ? abs : [name];
16222
+ }
16223
+ var DEFAULT_MAX_CONCURRENT_SUBTASKS = 4;
16224
+ function resolveMaxConcurrentSubtasks(settingsRaw = {}) {
16225
+ const clamp = (n) => Math.min(Math.floor(n), 16);
16226
+ const fromEnv = Number(process.env.NEXRALL_MAX_CONCURRENT_SUBTASKS);
16227
+ if (Number.isFinite(fromEnv) && fromEnv > 0)
16228
+ return clamp(fromEnv);
16229
+ const fromSettings = Number(settingsRaw.maxConcurrentSubtasks);
16230
+ if (Number.isFinite(fromSettings) && fromSettings > 0)
16231
+ return clamp(fromSettings);
16232
+ return DEFAULT_MAX_CONCURRENT_SUBTASKS;
16233
+ }
16123
16234
  function createLimiter(max) {
16124
16235
  let active = 0;
16125
16236
  const queue = [];
@@ -16138,7 +16249,20 @@ ${ctx.repeatError}
16138
16249
  }
16139
16250
  };
16140
16251
  }
16141
- var _subTaskLimit = createLimiter(MAX_CONCURRENT_SUBTASKS);
16252
+ var _subTaskLimitInstance = null;
16253
+ var _subTaskLimitMax = 0;
16254
+ var _inFlightSubTasks = 0;
16255
+ function subTaskLimiter(workDir) {
16256
+ if (!_subTaskLimitInstance) {
16257
+ _subTaskLimitMax = resolveMaxConcurrentSubtasks(workDir ? (0, rules_1.loadSettings)(workDir).raw : {});
16258
+ _subTaskLimitInstance = createLimiter(_subTaskLimitMax);
16259
+ }
16260
+ return { run: _subTaskLimitInstance, max: _subTaskLimitMax };
16261
+ }
16262
+ function _resetSubTaskLimiter() {
16263
+ _subTaskLimitInstance = null;
16264
+ _subTaskLimitMax = 0;
16265
+ }
16142
16266
  function humanDescription(name, input) {
16143
16267
  switch (name) {
16144
16268
  case "read_file":
@@ -16222,7 +16346,17 @@ ${ctx.repeatError}
16222
16346
  }
16223
16347
  var MAX_TASK_DEPTH = 1;
16224
16348
  var _subTaskCounter = 0;
16225
- var SUBTASK_TIMEOUT_MS = Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS) > 0 ? Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS) : 10 * 60 * 1e3;
16349
+ var DEFAULT_SUBTASK_TIMEOUT_MS = 10 * 60 * 1e3;
16350
+ function resolveSubtaskTimeoutMs(settingsRaw) {
16351
+ const fromEnv = Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS);
16352
+ if (Number.isFinite(fromEnv) && fromEnv > 0)
16353
+ return Math.floor(fromEnv);
16354
+ const raw = settingsRaw.subtaskTimeoutMs;
16355
+ const fromSettings = Number(raw);
16356
+ if (Number.isFinite(fromSettings) && fromSettings > 0)
16357
+ return Math.floor(fromSettings);
16358
+ return DEFAULT_SUBTASK_TIMEOUT_MS;
16359
+ }
16226
16360
  var SUBTASK_MAX = 48e3;
16227
16361
  var ToolNotAllowedError = class extends Error {
16228
16362
  constructor(message) {
@@ -16279,11 +16413,59 @@ ${tail}`;
16279
16413
  }
16280
16414
  if (toolNames.length === 0)
16281
16415
  return "";
16416
+ const recentFindings = lastToolResults(messages, SALVAGE_RESULT_COUNT, SALVAGE_RESULT_CHARS);
16282
16417
  const counts = /* @__PURE__ */ new Map();
16283
16418
  for (const n of toolNames)
16284
16419
  counts.set(n, (counts.get(n) ?? 0) + 1);
16285
16420
  const inventory = [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([name, n]) => n > 1 ? `${name} \xD7${n}` : name).join(", ");
16286
- return `Tool calls completed before it was stopped (${toolNames.length} total): ${inventory}.`;
16421
+ const header = `Tool calls completed before it was stopped (${toolNames.length} total): ${inventory}.`;
16422
+ return recentFindings ? `${header}
16423
+
16424
+ What its most recent tool calls actually returned (use this instead of repeating them):
16425
+ ${recentFindings}` : header;
16426
+ }
16427
+ var SALVAGE_RESULT_COUNT = 4;
16428
+ var SALVAGE_RESULT_CHARS = 2e3;
16429
+ function lastToolResults(messages, count, maxChars) {
16430
+ const nameById = /* @__PURE__ */ new Map();
16431
+ for (const m2 of messages) {
16432
+ if (m2.role !== "assistant" || !Array.isArray(m2.content))
16433
+ continue;
16434
+ for (const b of m2.content) {
16435
+ if (b?.type === "tool_use" && b.id && typeof b.name === "string")
16436
+ nameById.set(b.id, b.name);
16437
+ }
16438
+ }
16439
+ const out = [];
16440
+ for (let i2 = messages.length - 1; i2 >= 0 && out.length < count; i2--) {
16441
+ const m2 = messages[i2];
16442
+ if (m2.role !== "user" || !Array.isArray(m2.content))
16443
+ continue;
16444
+ for (const b of [...m2.content].reverse()) {
16445
+ if (out.length >= count)
16446
+ break;
16447
+ if (b?.type !== "tool_result")
16448
+ continue;
16449
+ const text = toolResultText(b);
16450
+ if (!text)
16451
+ continue;
16452
+ const name = nameById.get(String(b.tool_use_id ?? "")) ?? "tool";
16453
+ const body = text.length > maxChars ? `${sliceSafeEnd(text, maxChars)}
16454
+ \u2026 [truncated]` : text;
16455
+ out.push(`\u2022 ${name}:
16456
+ ${body}`);
16457
+ }
16458
+ }
16459
+ return out.reverse().join("\n\n");
16460
+ }
16461
+ function toolResultText(block) {
16462
+ const c = block.content;
16463
+ if (typeof c === "string")
16464
+ return c.trim();
16465
+ if (Array.isArray(c)) {
16466
+ return c.filter((x2) => x2?.type === "text" && typeof x2.text === "string").map((x2) => x2.text).join("\n").trim();
16467
+ }
16468
+ return "";
16287
16469
  }
16288
16470
  async function runSubTask(input, options, agentTypes) {
16289
16471
  const prompt2 = typeof input.prompt === "string" ? input.prompt.trim() : "";
@@ -16324,16 +16506,17 @@ If you just created .nexrall/agents/` + requestedType + ".md, make sure the writ
16324
16506
  }
16325
16507
  const memoryScope = agent?.memory;
16326
16508
  const agentMemoryNotes = agent && memoryScope ? (0, memory_1.agentMemoryPreamble)(agent.name, memoryScope, options.workDir) : "";
16509
+ const inheritedMd = agent?.lightPrompt ? "" : options.nexrallMd ?? "";
16327
16510
  const subNexrallMd = agent ? `# Sub-agent role: ${agent.name}
16328
16511
  ${agent.prompt}` + (agentMemoryNotes ? `
16329
16512
 
16330
16513
  ---
16331
16514
 
16332
- ${agentMemoryNotes}` : "") + (options.nexrallMd ? `
16515
+ ${agentMemoryNotes}` : "") + (inheritedMd ? `
16333
16516
 
16334
16517
  ---
16335
16518
 
16336
- ${options.nexrallMd}` : "") : options.nexrallMd;
16519
+ ${inheritedMd}` : "") : options.nexrallMd;
16337
16520
  const allowed = agent?.tools ? new Set(agent.tools) : null;
16338
16521
  if (allowed && memoryScope)
16339
16522
  allowed.add(exports.AGENT_MEMORY_TOOL);
@@ -16351,9 +16534,18 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
16351
16534
  };
16352
16535
  const subMessages = resumed ? [...resumed.messages, { role: "user", content: [{ type: "text", text: prompt2 }] }] : [{ role: "user", content: [{ type: "text", text: prompt2 }] }];
16353
16536
  const subAbort = { aborted: false };
16354
- const timer = setTimeout(() => {
16355
- subAbort.aborted = true;
16356
- }, SUBTASK_TIMEOUT_MS);
16537
+ const subtaskTimeoutMs = resolveSubtaskTimeoutMs((0, rules_1.loadSettings)(options.workDir).raw);
16538
+ let lastProgressAt = Date.now();
16539
+ let stalled = false;
16540
+ const bumpProgress = () => {
16541
+ lastProgressAt = Date.now();
16542
+ };
16543
+ const stallWatchdog = setInterval(() => {
16544
+ if (Date.now() - lastProgressAt > subtaskTimeoutMs) {
16545
+ stalled = true;
16546
+ subAbort.aborted = true;
16547
+ }
16548
+ }, 1e3);
16357
16549
  const parentAbortPoll = setInterval(() => {
16358
16550
  if (options.abortSignal?.aborted)
16359
16551
  subAbort.aborted = true;
@@ -16364,11 +16556,27 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
16364
16556
  _depth: depth + 1,
16365
16557
  _agentScope: `sub_${++_subTaskCounter}`,
16366
16558
  // isolated todo store per sub-agent
16367
- // Only set when the agent opted in, so the tool is inert for everyone else.
16368
- ...agent && memoryScope ? { _agentMemory: { agentName: agent.name, scope: memoryScope } } : {},
16559
+ // ── Assigned UNCONDITIONALLY, never by conditional spread ────────────────
16560
+ //
16561
+ // These two were previously spread in only when set:
16562
+ //
16563
+ // ...(agent && memoryScope ? { _agentMemory: … } : {}),
16564
+ //
16565
+ // which does NOT clear the key — it leaves whatever `...options` already had.
16566
+ // So a child WITHOUT its own `memory:` inherited its PARENT's binding and would
16567
+ // have appended to another agent's private notes; likewise an agent with no
16568
+ // `tools:` line inherited the parent's allowlist, making the prompt's capability
16569
+ // claim disagree with its real one.
16570
+ //
16571
+ // MAX_TASK_DEPTH === 1 means no nested spawn can reach this today, so it is
16572
+ // latent rather than live — but the limiter comment below explicitly contemplates
16573
+ // raising that depth, and this is exactly the kind of leak that would come back
16574
+ // as a security bug rather than a visible error. Explicit undefined makes the
16575
+ // child's identity independent of the parent's by construction.
16576
+ _agentMemory: agent && memoryScope ? { agentName: agent.name, scope: memoryScope } : void 0,
16369
16577
  // The same set `gatedPermission` enforces above, so prompt and permission agree
16370
16578
  // by construction instead of by two people remembering to update both.
16371
- ...allowed ? { _allowedTools: allowed } : {},
16579
+ _allowedTools: allowed ?? void 0,
16372
16580
  editorContext: null,
16373
16581
  // fresh isolated context for sub-agent
16374
16582
  model: agent?.model ?? options.model,
@@ -16400,25 +16608,48 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
16400
16608
  // Forward tool events with isSubTask=true so the UI can render a badge
16401
16609
  // instead of prepending "[sub-task]" to the tool name (which caused double-prefix
16402
16610
  // when the name was already labelled, and mixed display concerns into the data layer).
16403
- onToolUse: (n, i2) => options.onToolUse(n, i2, true),
16404
- onToolResult: (n, r2) => options.onToolResult(n, r2, true),
16611
+ // Every tool event is PROGRESS: it proves the sub-agent is still doing work, which
16612
+ // is what the stall watchdog above measures. Bumping on both use and result means a
16613
+ // single very slow tool (a long test run) resets the clock when it starts AND when
16614
+ // it finishes, so it cannot be mistaken for a hang.
16615
+ onToolUse: (n, i2) => {
16616
+ bumpProgress();
16617
+ options.onToolUse(n, i2, true);
16618
+ },
16619
+ onToolResult: (n, r2) => {
16620
+ bumpProgress();
16621
+ options.onToolResult(n, r2, true);
16622
+ },
16405
16623
  onToolStreamChunk: (n, c) => options.onToolStreamChunk?.(n, c, true),
16406
16624
  // Forward thinking so the UI shows the indicator while sub-agent reasons
16407
- onThinking: (text2) => options.onThinking?.(text2),
16408
- onThinkingDelta: (text2) => options.onThinkingDelta?.(text2),
16409
- onThinkingProgress: (tok) => options.onThinkingProgress?.(tok)
16625
+ // Thinking is progress too — a model reasoning for minutes on a hard problem is
16626
+ // working, not stalled. Without this, deep reasoning on an expensive tier would
16627
+ // trip the watchdog precisely when the sub-agent was most valuable.
16628
+ onThinking: (text2) => {
16629
+ bumpProgress();
16630
+ options.onThinking?.(text2);
16631
+ },
16632
+ onThinkingDelta: (text2) => {
16633
+ bumpProgress();
16634
+ options.onThinkingDelta?.(text2);
16635
+ },
16636
+ onThinkingProgress: (tok) => {
16637
+ bumpProgress();
16638
+ options.onThinkingProgress?.(tok);
16639
+ }
16410
16640
  });
16411
- if (subAbort.aborted && !options.abortSignal?.aborted) {
16412
- const mins = Math.round(SUBTASK_TIMEOUT_MS / 6e4);
16641
+ if (stalled && !options.abortSignal?.aborted) {
16642
+ const mins = Math.round(subtaskTimeoutMs / 6e4);
16413
16643
  const partial = capSubTaskText(extractSubTaskText(result, false));
16414
16644
  const progress = summariseSubTaskProgress(result);
16645
+ const partialId = (0, agentRegistry_1.rememberAgent)(agent?.name ?? null, typeof input.description === "string" && input.description.trim() || prompt2.slice(0, 80), result);
16415
16646
  const sections = [
16416
- `Sub-task STOPPED after ${mins} minutes without completing \u2014 treat the following as PARTIAL, unverified work, not a finished answer.`,
16647
+ `Sub-task STOPPED after ${mins} minutes with NO PROGRESS (it was not making tool calls or producing output) \u2014 treat everything below as PARTIAL, unverified work, not a finished answer.`,
16417
16648
  progress,
16418
16649
  partial ? `Partial output before it was stopped:
16419
16650
 
16420
16651
  ${partial}` : "",
16421
- "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."
16652
+ `Do NOT re-run the same sub-task from scratch. Either build on what is above, or continue THIS run with resume_agent_id="${partialId}" (it still has everything it read), or split the remaining work into smaller, more focused sub-tasks.`
16422
16653
  ].filter(Boolean);
16423
16654
  return { error: sections.join("\n\n") };
16424
16655
  }
@@ -16447,7 +16678,7 @@ ${partial}` : "",
16447
16678
  }
16448
16679
  return { error: `Sub-task failed: ${err.message}` };
16449
16680
  } finally {
16450
- clearTimeout(timer);
16681
+ clearInterval(stallWatchdog);
16451
16682
  clearInterval(parentAbortPoll);
16452
16683
  }
16453
16684
  }
@@ -17120,7 +17351,20 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
17120
17351
  if (!permitted) {
17121
17352
  result = { error: deniedReason ?? "Permission denied by user" };
17122
17353
  } else if (name === "task") {
17123
- result = depth === 0 ? await _subTaskLimit(() => runSubTask(input, options, agentTypes)) : await runSubTask(input, options, agentTypes);
17354
+ if (depth === 0) {
17355
+ const { run: limitRun, max: limitMax } = subTaskLimiter(options.workDir);
17356
+ if (_inFlightSubTasks >= limitMax) {
17357
+ (options.onNotice ?? options.onText)(`\u23F3 Queued: ${limitMax} sub-agents are already running, so this one starts when a slot frees up (raise "maxConcurrentSubtasks" in .nexrall/settings.json to widen it).`);
17358
+ }
17359
+ _inFlightSubTasks++;
17360
+ try {
17361
+ result = await limitRun(() => runSubTask(input, options, agentTypes));
17362
+ } finally {
17363
+ _inFlightSubTasks--;
17364
+ }
17365
+ } else {
17366
+ result = await runSubTask(input, options, agentTypes);
17367
+ }
17124
17368
  } else {
17125
17369
  const pre = runToolHooks(hooks.PreToolUse, "PreToolUse", name, input, options.workDir);
17126
17370
  if (pre.block) {
@@ -17140,15 +17384,10 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
17140
17384
  result = { output: mcpOutput ?? "" };
17141
17385
  } else {
17142
17386
  options.checkpointManager?.recordBeforeMutation(name, input);
17143
- const WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "edit_file", "multi_edit", "delete_file", "move_file", "copy_file", "notebook_edit"]);
17144
17387
  const onStream = options.onToolStreamChunk ? (chunk) => options.onToolStreamChunk(name, chunk) : void 0;
17145
- if (WRITE_TOOLS.has(name)) {
17146
- const targetPath = typeof input.path === "string" ? input.path : typeof input.source === "string" ? input.source : "";
17147
- const absTarget = targetPath && options.workDir ? path6.resolve(options.workDir, targetPath) : targetPath;
17148
- result = await withFileLock(absTarget || name, () => (0, executor_1.executeTool)(name, input, options.abortSignal, sandboxCfg, options.workDir, agentScope, onStream));
17149
- } else {
17150
- result = await (0, executor_1.executeTool)(name, input, options.abortSignal, sandboxCfg, options.workDir, agentScope, onStream);
17151
- }
17388
+ const run = () => (0, executor_1.executeTool)(name, input, options.abortSignal, sandboxCfg, options.workDir, agentScope, onStream);
17389
+ const locks = name === "bash" ? bashNeedsRepoLock(String(input.command ?? "")) ? [REPO_STATE_LOCK] : [] : lockPathsFor(name, input, options.workDir);
17390
+ result = locks.length ? await withFileLocks(locks, run) : await run();
17152
17391
  }
17153
17392
  } catch (err) {
17154
17393
  result = { error: `Tool execution failed: ${err.message}` };
@@ -54573,22 +54812,30 @@ var _replInterface = null;
54573
54812
  function setReadlineInterface(rl) {
54574
54813
  _replInterface = rl;
54575
54814
  }
54815
+ var _promptChain = Promise.resolve();
54816
+ function serialisePrompt(fn) {
54817
+ const next = _promptChain.then(fn, fn);
54818
+ _promptChain = next.catch(() => void 0);
54819
+ return next;
54820
+ }
54576
54821
  function askUser(question) {
54577
- if (_replInterface) {
54578
- const rl = _replInterface;
54822
+ return serialisePrompt(() => {
54823
+ if (_replInterface) {
54824
+ const rl = _replInterface;
54825
+ return new Promise((resolve3) => {
54826
+ rl.question(question, (answer) => resolve3(answer.trim()));
54827
+ });
54828
+ }
54579
54829
  return new Promise((resolve3) => {
54580
- rl.question(question, (answer) => resolve3(answer.trim()));
54581
- });
54582
- }
54583
- return new Promise((resolve3) => {
54584
- const rl = readline2.createInterface({
54585
- input: process.stdin,
54586
- output: process.stderr,
54587
- terminal: true
54588
- });
54589
- rl.question(question, (answer) => {
54590
- rl.close();
54591
- resolve3(answer.trim());
54830
+ const rl = readline2.createInterface({
54831
+ input: process.stdin,
54832
+ output: process.stderr,
54833
+ terminal: true
54834
+ });
54835
+ rl.question(question, (answer) => {
54836
+ rl.close();
54837
+ resolve3(answer.trim());
54838
+ });
54592
54839
  });
54593
54840
  });
54594
54841
  }
@@ -64214,7 +64461,7 @@ var InkReadlineAdapter = class extends EventEmitter3 {
64214
64461
  };
64215
64462
 
64216
64463
  // src/commands/chat.ts
64217
- var CLI_VERSION = "0.5.54";
64464
+ var CLI_VERSION = "0.5.56";
64218
64465
  var MODEL_LABELS = {
64219
64466
  turbo: "Nexrall Turbo",
64220
64467
  pro: "Nexrall Pro",
@@ -65881,7 +66128,7 @@ function pluginSourceRemoveCommand(name, opts) {
65881
66128
 
65882
66129
  // src/index.ts
65883
66130
  var program2 = new Command();
65884
- program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.54").enablePositionalOptions();
66131
+ program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.56").enablePositionalOptions();
65885
66132
  program2.command("auth").description("Login to your Nexrall account").action(authCommand);
65886
66133
  program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
65887
66134
  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.54",
3
+ "version": "0.5.56",
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.29"
44
+ "@nexrall/code-core": "1.4.31"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@aws-sdk/client-s3": "^3.600.0",