nexrall-code 0.5.54 → 0.5.55

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 +157 -22
  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",
@@ -15877,9 +15903,11 @@ var require_loop = __commonJS({
15877
15903
  exports.stopReasonNotice = stopReasonNotice;
15878
15904
  exports.resolveMaxIterations = resolveMaxIterations;
15879
15905
  exports.createLimiter = createLimiter;
15906
+ exports.resolveSubtaskTimeoutMs = resolveSubtaskTimeoutMs;
15880
15907
  exports.extractSubTaskText = extractSubTaskText;
15881
15908
  exports.capSubTaskText = capSubTaskText;
15882
15909
  exports.summariseSubTaskProgress = summariseSubTaskProgress;
15910
+ exports.lastToolResults = lastToolResults;
15883
15911
  exports.contextWindowFor = contextWindowFor2;
15884
15912
  exports.compactionThresholds = compactionThresholds2;
15885
15913
  exports.estimateBodyBytes = estimateBodyBytes2;
@@ -16222,7 +16250,17 @@ ${ctx.repeatError}
16222
16250
  }
16223
16251
  var MAX_TASK_DEPTH = 1;
16224
16252
  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;
16253
+ var DEFAULT_SUBTASK_TIMEOUT_MS = 10 * 60 * 1e3;
16254
+ function resolveSubtaskTimeoutMs(settingsRaw) {
16255
+ const fromEnv = Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS);
16256
+ if (Number.isFinite(fromEnv) && fromEnv > 0)
16257
+ return Math.floor(fromEnv);
16258
+ const raw = settingsRaw.subtaskTimeoutMs;
16259
+ const fromSettings = Number(raw);
16260
+ if (Number.isFinite(fromSettings) && fromSettings > 0)
16261
+ return Math.floor(fromSettings);
16262
+ return DEFAULT_SUBTASK_TIMEOUT_MS;
16263
+ }
16226
16264
  var SUBTASK_MAX = 48e3;
16227
16265
  var ToolNotAllowedError = class extends Error {
16228
16266
  constructor(message) {
@@ -16279,11 +16317,59 @@ ${tail}`;
16279
16317
  }
16280
16318
  if (toolNames.length === 0)
16281
16319
  return "";
16320
+ const recentFindings = lastToolResults(messages, SALVAGE_RESULT_COUNT, SALVAGE_RESULT_CHARS);
16282
16321
  const counts = /* @__PURE__ */ new Map();
16283
16322
  for (const n of toolNames)
16284
16323
  counts.set(n, (counts.get(n) ?? 0) + 1);
16285
16324
  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}.`;
16325
+ const header = `Tool calls completed before it was stopped (${toolNames.length} total): ${inventory}.`;
16326
+ return recentFindings ? `${header}
16327
+
16328
+ What its most recent tool calls actually returned (use this instead of repeating them):
16329
+ ${recentFindings}` : header;
16330
+ }
16331
+ var SALVAGE_RESULT_COUNT = 4;
16332
+ var SALVAGE_RESULT_CHARS = 2e3;
16333
+ function lastToolResults(messages, count, maxChars) {
16334
+ const nameById = /* @__PURE__ */ new Map();
16335
+ for (const m2 of messages) {
16336
+ if (m2.role !== "assistant" || !Array.isArray(m2.content))
16337
+ continue;
16338
+ for (const b of m2.content) {
16339
+ if (b?.type === "tool_use" && b.id && typeof b.name === "string")
16340
+ nameById.set(b.id, b.name);
16341
+ }
16342
+ }
16343
+ const out = [];
16344
+ for (let i2 = messages.length - 1; i2 >= 0 && out.length < count; i2--) {
16345
+ const m2 = messages[i2];
16346
+ if (m2.role !== "user" || !Array.isArray(m2.content))
16347
+ continue;
16348
+ for (const b of [...m2.content].reverse()) {
16349
+ if (out.length >= count)
16350
+ break;
16351
+ if (b?.type !== "tool_result")
16352
+ continue;
16353
+ const text = toolResultText(b);
16354
+ if (!text)
16355
+ continue;
16356
+ const name = nameById.get(String(b.tool_use_id ?? "")) ?? "tool";
16357
+ const body = text.length > maxChars ? `${sliceSafeEnd(text, maxChars)}
16358
+ \u2026 [truncated]` : text;
16359
+ out.push(`\u2022 ${name}:
16360
+ ${body}`);
16361
+ }
16362
+ }
16363
+ return out.reverse().join("\n\n");
16364
+ }
16365
+ function toolResultText(block) {
16366
+ const c = block.content;
16367
+ if (typeof c === "string")
16368
+ return c.trim();
16369
+ if (Array.isArray(c)) {
16370
+ return c.filter((x2) => x2?.type === "text" && typeof x2.text === "string").map((x2) => x2.text).join("\n").trim();
16371
+ }
16372
+ return "";
16287
16373
  }
16288
16374
  async function runSubTask(input, options, agentTypes) {
16289
16375
  const prompt2 = typeof input.prompt === "string" ? input.prompt.trim() : "";
@@ -16324,16 +16410,17 @@ If you just created .nexrall/agents/` + requestedType + ".md, make sure the writ
16324
16410
  }
16325
16411
  const memoryScope = agent?.memory;
16326
16412
  const agentMemoryNotes = agent && memoryScope ? (0, memory_1.agentMemoryPreamble)(agent.name, memoryScope, options.workDir) : "";
16413
+ const inheritedMd = agent?.lightPrompt ? "" : options.nexrallMd ?? "";
16327
16414
  const subNexrallMd = agent ? `# Sub-agent role: ${agent.name}
16328
16415
  ${agent.prompt}` + (agentMemoryNotes ? `
16329
16416
 
16330
16417
  ---
16331
16418
 
16332
- ${agentMemoryNotes}` : "") + (options.nexrallMd ? `
16419
+ ${agentMemoryNotes}` : "") + (inheritedMd ? `
16333
16420
 
16334
16421
  ---
16335
16422
 
16336
- ${options.nexrallMd}` : "") : options.nexrallMd;
16423
+ ${inheritedMd}` : "") : options.nexrallMd;
16337
16424
  const allowed = agent?.tools ? new Set(agent.tools) : null;
16338
16425
  if (allowed && memoryScope)
16339
16426
  allowed.add(exports.AGENT_MEMORY_TOOL);
@@ -16351,9 +16438,18 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
16351
16438
  };
16352
16439
  const subMessages = resumed ? [...resumed.messages, { role: "user", content: [{ type: "text", text: prompt2 }] }] : [{ role: "user", content: [{ type: "text", text: prompt2 }] }];
16353
16440
  const subAbort = { aborted: false };
16354
- const timer = setTimeout(() => {
16355
- subAbort.aborted = true;
16356
- }, SUBTASK_TIMEOUT_MS);
16441
+ const subtaskTimeoutMs = resolveSubtaskTimeoutMs((0, rules_1.loadSettings)(options.workDir).raw);
16442
+ let lastProgressAt = Date.now();
16443
+ let stalled = false;
16444
+ const bumpProgress = () => {
16445
+ lastProgressAt = Date.now();
16446
+ };
16447
+ const stallWatchdog = setInterval(() => {
16448
+ if (Date.now() - lastProgressAt > subtaskTimeoutMs) {
16449
+ stalled = true;
16450
+ subAbort.aborted = true;
16451
+ }
16452
+ }, 1e3);
16357
16453
  const parentAbortPoll = setInterval(() => {
16358
16454
  if (options.abortSignal?.aborted)
16359
16455
  subAbort.aborted = true;
@@ -16364,11 +16460,27 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
16364
16460
  _depth: depth + 1,
16365
16461
  _agentScope: `sub_${++_subTaskCounter}`,
16366
16462
  // 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 } } : {},
16463
+ // ── Assigned UNCONDITIONALLY, never by conditional spread ────────────────
16464
+ //
16465
+ // These two were previously spread in only when set:
16466
+ //
16467
+ // ...(agent && memoryScope ? { _agentMemory: … } : {}),
16468
+ //
16469
+ // which does NOT clear the key — it leaves whatever `...options` already had.
16470
+ // So a child WITHOUT its own `memory:` inherited its PARENT's binding and would
16471
+ // have appended to another agent's private notes; likewise an agent with no
16472
+ // `tools:` line inherited the parent's allowlist, making the prompt's capability
16473
+ // claim disagree with its real one.
16474
+ //
16475
+ // MAX_TASK_DEPTH === 1 means no nested spawn can reach this today, so it is
16476
+ // latent rather than live — but the limiter comment below explicitly contemplates
16477
+ // raising that depth, and this is exactly the kind of leak that would come back
16478
+ // as a security bug rather than a visible error. Explicit undefined makes the
16479
+ // child's identity independent of the parent's by construction.
16480
+ _agentMemory: agent && memoryScope ? { agentName: agent.name, scope: memoryScope } : void 0,
16369
16481
  // The same set `gatedPermission` enforces above, so prompt and permission agree
16370
16482
  // by construction instead of by two people remembering to update both.
16371
- ...allowed ? { _allowedTools: allowed } : {},
16483
+ _allowedTools: allowed ?? void 0,
16372
16484
  editorContext: null,
16373
16485
  // fresh isolated context for sub-agent
16374
16486
  model: agent?.model ?? options.model,
@@ -16400,25 +16512,48 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
16400
16512
  // Forward tool events with isSubTask=true so the UI can render a badge
16401
16513
  // instead of prepending "[sub-task]" to the tool name (which caused double-prefix
16402
16514
  // 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),
16515
+ // Every tool event is PROGRESS: it proves the sub-agent is still doing work, which
16516
+ // is what the stall watchdog above measures. Bumping on both use and result means a
16517
+ // single very slow tool (a long test run) resets the clock when it starts AND when
16518
+ // it finishes, so it cannot be mistaken for a hang.
16519
+ onToolUse: (n, i2) => {
16520
+ bumpProgress();
16521
+ options.onToolUse(n, i2, true);
16522
+ },
16523
+ onToolResult: (n, r2) => {
16524
+ bumpProgress();
16525
+ options.onToolResult(n, r2, true);
16526
+ },
16405
16527
  onToolStreamChunk: (n, c) => options.onToolStreamChunk?.(n, c, true),
16406
16528
  // 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)
16529
+ // Thinking is progress too — a model reasoning for minutes on a hard problem is
16530
+ // working, not stalled. Without this, deep reasoning on an expensive tier would
16531
+ // trip the watchdog precisely when the sub-agent was most valuable.
16532
+ onThinking: (text2) => {
16533
+ bumpProgress();
16534
+ options.onThinking?.(text2);
16535
+ },
16536
+ onThinkingDelta: (text2) => {
16537
+ bumpProgress();
16538
+ options.onThinkingDelta?.(text2);
16539
+ },
16540
+ onThinkingProgress: (tok) => {
16541
+ bumpProgress();
16542
+ options.onThinkingProgress?.(tok);
16543
+ }
16410
16544
  });
16411
- if (subAbort.aborted && !options.abortSignal?.aborted) {
16412
- const mins = Math.round(SUBTASK_TIMEOUT_MS / 6e4);
16545
+ if (stalled && !options.abortSignal?.aborted) {
16546
+ const mins = Math.round(subtaskTimeoutMs / 6e4);
16413
16547
  const partial = capSubTaskText(extractSubTaskText(result, false));
16414
16548
  const progress = summariseSubTaskProgress(result);
16549
+ const partialId = (0, agentRegistry_1.rememberAgent)(agent?.name ?? null, typeof input.description === "string" && input.description.trim() || prompt2.slice(0, 80), result);
16415
16550
  const sections = [
16416
- `Sub-task STOPPED after ${mins} minutes without completing \u2014 treat the following as PARTIAL, unverified work, not a finished answer.`,
16551
+ `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
16552
  progress,
16418
16553
  partial ? `Partial output before it was stopped:
16419
16554
 
16420
16555
  ${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."
16556
+ `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
16557
  ].filter(Boolean);
16423
16558
  return { error: sections.join("\n\n") };
16424
16559
  }
@@ -16447,7 +16582,7 @@ ${partial}` : "",
16447
16582
  }
16448
16583
  return { error: `Sub-task failed: ${err.message}` };
16449
16584
  } finally {
16450
- clearTimeout(timer);
16585
+ clearInterval(stallWatchdog);
16451
16586
  clearInterval(parentAbortPoll);
16452
16587
  }
16453
16588
  }
@@ -64214,7 +64349,7 @@ var InkReadlineAdapter = class extends EventEmitter3 {
64214
64349
  };
64215
64350
 
64216
64351
  // src/commands/chat.ts
64217
- var CLI_VERSION = "0.5.54";
64352
+ var CLI_VERSION = "0.5.55";
64218
64353
  var MODEL_LABELS = {
64219
64354
  turbo: "Nexrall Turbo",
64220
64355
  pro: "Nexrall Pro",
@@ -65881,7 +66016,7 @@ function pluginSourceRemoveCommand(name, opts) {
65881
66016
 
65882
66017
  // src/index.ts
65883
66018
  var program2 = new Command();
65884
- program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.54").enablePositionalOptions();
66019
+ program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.55").enablePositionalOptions();
65885
66020
  program2.command("auth").description("Login to your Nexrall account").action(authCommand);
65886
66021
  program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
65887
66022
  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.55",
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.30"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@aws-sdk/client-s3": "^3.600.0",