nexrall-code 0.5.114 → 0.5.115

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 +170 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3078,9 +3078,15 @@ var require_types = __commonJS({
3078
3078
  "../core/dist/types.js"(exports2) {
3079
3079
  "use strict";
3080
3080
  Object.defineProperty(exports2, "__esModule", { value: true });
3081
- exports2.AgentTurnError = void 0;
3081
+ exports2.AgentTurnError = exports2.RUNTIME_CONTEXT_OPEN_TAG = void 0;
3082
+ exports2.isRuntimeContextBlock = isRuntimeContextBlock;
3082
3083
  exports2.isAgentTurnError = isAgentTurnError;
3083
3084
  exports2.salvageHistory = salvageHistory2;
3085
+ exports2.RUNTIME_CONTEXT_OPEN_TAG = "<runtime_context>";
3086
+ function isRuntimeContextBlock(b) {
3087
+ const x2 = b;
3088
+ return !!x2 && x2.type === "text" && typeof x2.text === "string" && x2.text.startsWith(exports2.RUNTIME_CONTEXT_OPEN_TAG);
3089
+ }
3084
3090
  var AgentTurnError = class extends Error {
3085
3091
  constructor(message, messages, progressCount, cause) {
3086
3092
  super(message);
@@ -10023,6 +10029,7 @@ var require_client = __commonJS({
10023
10029
  var RETRY_MAX_MS = 3e4;
10024
10030
  var HOUSEKEEPING_FRAMES = /* @__PURE__ */ new Set([
10025
10031
  "resumable",
10032
+ "runtime_context",
10026
10033
  "context_warning",
10027
10034
  "tool_progress"
10028
10035
  ]);
@@ -10524,6 +10531,19 @@ var require_client = __commonJS({
10524
10531
  serverResumable = true;
10525
10532
  break;
10526
10533
  }
10534
+ case "tool_use_ready": {
10535
+ if (typeof evt.id === "string" && typeof evt.name === "string") {
10536
+ const input = evt.input && typeof evt.input === "object" ? evt.input : {};
10537
+ onEvent({ type: "tool_use_ready", id: evt.id, name: evt.name, input });
10538
+ }
10539
+ break;
10540
+ }
10541
+ case "runtime_context": {
10542
+ if (typeof evt.text === "string" && evt.text) {
10543
+ onEvent({ type: "runtime_context", text: evt.text });
10544
+ }
10545
+ break;
10546
+ }
10527
10547
  case "balance_status": {
10528
10548
  const balance = typeof evt.balance === "number" ? evt.balance : 0;
10529
10549
  const zero = !!evt.zero;
@@ -103978,6 +103998,88 @@ var require_readDedupe = __commonJS({
103978
103998
  }
103979
103999
  });
103980
104000
 
104001
+ // ../core/dist/agent/toolPrefetch.js
104002
+ var require_toolPrefetch = __commonJS({
104003
+ "../core/dist/agent/toolPrefetch.js"(exports2) {
104004
+ "use strict";
104005
+ Object.defineProperty(exports2, "__esModule", { value: true });
104006
+ exports2.ToolPrefetcher = exports2.PREFETCHABLE_TOOLS = void 0;
104007
+ exports2.isPrefetchSafeMessage = isPrefetchSafeMessage;
104008
+ exports2.toolPrefetchEnabled = toolPrefetchEnabled;
104009
+ exports2.PREFETCHABLE_TOOLS = /* @__PURE__ */ new Set([
104010
+ "read_file",
104011
+ "list_directory",
104012
+ "glob",
104013
+ "search_files"
104014
+ ]);
104015
+ var NON_MUTATING_COMPANIONS = /* @__PURE__ */ new Set([
104016
+ ...exports2.PREFETCHABLE_TOOLS,
104017
+ "notebook_read",
104018
+ "todo_write",
104019
+ "todo_read",
104020
+ "memory_read",
104021
+ "use_skill",
104022
+ "fetch_url",
104023
+ "web_search",
104024
+ "get_diagnostics",
104025
+ "go_to_definition",
104026
+ "find_references",
104027
+ "get_symbols",
104028
+ "get_workspace_symbols",
104029
+ "get_hover"
104030
+ ]);
104031
+ function isPrefetchSafeMessage(toolNames) {
104032
+ return toolNames.every((n) => NON_MUTATING_COMPANIONS.has(n));
104033
+ }
104034
+ function toolPrefetchEnabled() {
104035
+ return process.env.NEXRALL_TOOL_PREFETCH !== "0";
104036
+ }
104037
+ function stableKey(value) {
104038
+ if (value === null || typeof value !== "object")
104039
+ return JSON.stringify(value) ?? "undefined";
104040
+ if (Array.isArray(value))
104041
+ return `[${value.map(stableKey).join(",")}]`;
104042
+ const obj2 = value;
104043
+ return `{${Object.keys(obj2).sort().map((k) => `${JSON.stringify(k)}:${stableKey(obj2[k])}`).join(",")}}`;
104044
+ }
104045
+ var ToolPrefetcher = class {
104046
+ constructor(run) {
104047
+ this.run = run;
104048
+ this.entries = /* @__PURE__ */ new Map();
104049
+ }
104050
+ /** Start `name` early if it is a pure read. Idempotent per id. */
104051
+ start(id, name, input) {
104052
+ if (!id || !exports2.PREFETCHABLE_TOOLS.has(name) || this.entries.has(id))
104053
+ return false;
104054
+ const promise = Promise.resolve().then(() => this.run(name, input)).catch((err) => ({ error: `Tool execution failed: ${err?.message ?? String(err)}` }));
104055
+ this.entries.set(id, { name, key: stableKey(input), promise });
104056
+ return true;
104057
+ }
104058
+ /**
104059
+ * The prefetched result for exactly this call, or null. Always removes the entry,
104060
+ * so a result is handed out at most once.
104061
+ */
104062
+ take(id, name, input) {
104063
+ const e2 = this.entries.get(id);
104064
+ if (!e2)
104065
+ return null;
104066
+ this.entries.delete(id);
104067
+ if (e2.name !== name || e2.key !== stableKey(input))
104068
+ return null;
104069
+ return e2.promise;
104070
+ }
104071
+ /** Drop everything (stream restarted — the ids it announced will never be dispatched). */
104072
+ clear() {
104073
+ this.entries.clear();
104074
+ }
104075
+ get size() {
104076
+ return this.entries.size;
104077
+ }
104078
+ };
104079
+ exports2.ToolPrefetcher = ToolPrefetcher;
104080
+ }
104081
+ });
104082
+
103981
104083
  // ../core/dist/agent/worktree.js
103982
104084
  var require_worktree = __commonJS({
103983
104085
  "../core/dist/agent/worktree.js"(exports2) {
@@ -106121,6 +106223,7 @@ var require_loop = __commonJS({
106121
106223
  exports2.estimateBodyBytes = estimateBodyBytes2;
106122
106224
  exports2.allowsTestOnlyWrite = allowsTestOnlyWrite;
106123
106225
  exports2.findSafeCutIndex = findSafeCutIndex;
106226
+ exports2.persistRuntimeContext = persistRuntimeContext;
106124
106227
  exports2.transcriptOf = transcriptOf;
106125
106228
  exports2.createLedger = createLedger;
106126
106229
  exports2.ledgerRecord = ledgerRecord;
@@ -106134,6 +106237,7 @@ var require_loop = __commonJS({
106134
106237
  exports2.runAgentLoop = runAgentLoop2;
106135
106238
  exports2.trimToResumableBoundary = trimToResumableBoundary;
106136
106239
  var readDedupe_1 = require_readDedupe();
106240
+ var toolPrefetch_1 = require_toolPrefetch();
106137
106241
  var worktree_1 = require_worktree();
106138
106242
  var backgroundAgents_1 = require_backgroundAgents();
106139
106243
  var types_1 = require_types();
@@ -107448,10 +107552,22 @@ ${partial}` : "",
107448
107552
  return -1;
107449
107553
  }
107450
107554
  var MAX_TRANSCRIPT_CHARS = 6e5;
107555
+ function persistRuntimeContext(messages, idx, text) {
107556
+ const m2 = messages[idx];
107557
+ if (!m2 || m2.role !== "user" || !Array.isArray(m2.content))
107558
+ return false;
107559
+ const lastBlock = m2.content[m2.content.length - 1];
107560
+ if (lastBlock && lastBlock.type === "text" && lastBlock.text === text)
107561
+ return false;
107562
+ messages[idx] = { ...m2, content: [...m2.content, { type: "text", text }] };
107563
+ return true;
107564
+ }
107451
107565
  function transcriptOf(messages) {
107452
107566
  const parts = [];
107453
107567
  for (const m2 of messages) {
107454
107568
  for (const b of m2.content) {
107569
+ if ((0, types_1.isRuntimeContextBlock)(b))
107570
+ continue;
107455
107571
  if (b.type === "text" && b.text) {
107456
107572
  parts.push(`${m2.role.toUpperCase()}: ${(0, safeSlice_1.sliceSafeEnd)(b.text, 2e3)}`);
107457
107573
  } else if (b.type === "tool_use") {
@@ -107604,7 +107720,7 @@ ${tail}`;
107604
107720
  const first = messages.find((m2) => m2.role === "user");
107605
107721
  if (!first || !Array.isArray(first.content))
107606
107722
  return "";
107607
- return first.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("\n").trim();
107723
+ return first.content.filter((b) => b.type === "text" && b.text && !(0, types_1.isRuntimeContextBlock)(b)).map((b) => b.text).join("\n").trim();
107608
107724
  }
107609
107725
  var CompactionUnavailableError = class extends Error {
107610
107726
  constructor() {
@@ -107662,7 +107778,14 @@ Do NOT call any tools and do NOT continue the task. Reply with text only: a conc
107662
107778
  // surprise charge on a different provider. Falls back to the same
107663
107779
  // default as the main loop (see `runAgentLoop`) only when no model was
107664
107780
  // set at all.
107665
- model: options.model ?? "turbo",
107781
+ //
107782
+ // Cheapest model of the SAME vendor (Claude Code runs this kind of work on
107783
+ // Haiku). Safe to downgrade HERE because this fallback sends a fresh
107784
+ // transcript — it shares no cached prefix with the conversation, unlike
107785
+ // summarizeCached above, which must stay on the conversation's own model.
107786
+ // Never crosses providers; falls back to the session model if the
107787
+ // catalogue is not loaded.
107788
+ model: (0, modelCatalogue_1.cheapestSameVendorModel)(options.model ?? "turbo") ?? options.model ?? "turbo",
107666
107789
  mode: "ask",
107667
107790
  // summariser must not call tools; ask-mode discourages action
107668
107791
  env: options.env,
@@ -107705,7 +107828,9 @@ Continue the work from here.` }] });
107705
107828
  // Same reasoning as autoCompactMessages' summariser: run on the same
107706
107829
  // model as the actual conversation rather than forcing a fixed tier
107707
107830
  // (which silently billed Anthropic regardless of the user's provider).
107708
- model: options.model ?? "turbo",
107831
+ // Cheapest SAME-vendor model: a fresh prompt with no shared cache prefix,
107832
+ // and merging a few memory bullets does not need the session's top tier.
107833
+ model: (0, modelCatalogue_1.cheapestSameVendorModel)(options.model ?? "turbo") ?? options.model ?? "turbo",
107709
107834
  mode: "ask",
107710
107835
  env: options.env,
107711
107836
  clientType: options.clientType,
@@ -107989,7 +108114,6 @@ Continue the work from here.` }] });
107989
108114
  if (reclaimed > 0) {
107990
108115
  bodyBytes = estimateBodyBytes2(messages);
107991
108116
  bytePressure = bodyBytes > MAX_BODY_BYTES;
107992
- (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.`);
107993
108117
  }
107994
108118
  }
107995
108119
  if (compactDisabled && bodyBytes > compactDisabledAtBytes * COMPACT_REARM_GROWTH) {
@@ -108014,7 +108138,6 @@ Continue the work from here.` }] });
108014
108138
  if (did) {
108015
108139
  lastPromptTokens = 0;
108016
108140
  const reason = bytePressure ? `body ~${(bodyBytes / (1024 * 1024)).toFixed(1)}MB` : "context window";
108017
- (options.onNotice ?? options.onText)(`\u267B\uFE0F Auto-compacted earlier conversation to stay within the ${reason}.`);
108018
108141
  bodyBytes = bytesAfter;
108019
108142
  bytePressure = bodyBytes > MAX_BODY_BYTES;
108020
108143
  }
@@ -108035,6 +108158,10 @@ Continue the work from here.` }] });
108035
108158
  \u26A0\uFE0F Approaching the ${budget}-step limit (step ${iteration + 1}). Please wrap up and summarise what has been done.
108036
108159
  `);
108037
108160
  }
108161
+ let pendingRuntimeContext = null;
108162
+ const sentUserIdx = messages.length - 1;
108163
+ const prefetcher = new toolPrefetch_1.ToolPrefetcher((toolName, toolInput) => (0, executor_1.executeTool)(toolName, toolInput, options.abortSignal, sandboxCfg, options.workDir, agentScope, void 0, options.selfPeer));
108164
+ const prefetchOn = (0, toolPrefetch_1.toolPrefetchEnabled)();
108038
108165
  const onEvent = (event) => {
108039
108166
  switch (event.type) {
108040
108167
  case "text":
@@ -108066,11 +108193,19 @@ Continue the work from here.` }] });
108066
108193
  options.onRetryResolved?.();
108067
108194
  break;
108068
108195
  case "stream_restart":
108196
+ prefetcher.clear();
108069
108197
  options.onStreamRestart?.(event.reason, event.discardedChars);
108070
108198
  break;
108071
108199
  case "balance_status":
108072
108200
  options.onBalanceStatus?.(event.balance, event.zero);
108073
108201
  break;
108202
+ case "runtime_context":
108203
+ pendingRuntimeContext = event.text;
108204
+ break;
108205
+ case "tool_use_ready":
108206
+ if (prefetchOn && !options.abortSignal?.aborted)
108207
+ prefetcher.start(event.id, event.name, event.input);
108208
+ break;
108074
108209
  case "done":
108075
108210
  break;
108076
108211
  case "error":
@@ -108090,6 +108225,8 @@ Continue the work from here.` }] });
108090
108225
  allowRestartAfterRender: !!options.onStreamRestart
108091
108226
  }, onEvent);
108092
108227
  options.onApiCallDuration?.(Date.now() - _apiCallStartedAt);
108228
+ if (pendingRuntimeContext)
108229
+ persistRuntimeContext(messages, sentUserIdx, pendingRuntimeContext);
108093
108230
  } catch (err) {
108094
108231
  options.onApiCallDuration?.(Date.now() - _apiCallStartedAt);
108095
108232
  if (options.abortSignal?.aborted || err.name === "AbortError") {
@@ -108244,6 +108381,7 @@ Continue the work from here.` }] });
108244
108381
  stopReason = "clean";
108245
108382
  break;
108246
108383
  }
108384
+ const prefetchSafe = prefetchOn && (0, toolPrefetch_1.isPrefetchSafeMessage)(toolUseBlocks.map((b) => b.name));
108247
108385
  const toolResults = await Promise.all(toolUseBlocks.map(async (block) => {
108248
108386
  const { id, name, input } = block;
108249
108387
  const toolMeta = { id };
@@ -108331,8 +108469,14 @@ Continue the work from here.` }] });
108331
108469
  const run = () => (0, executor_1.executeTool)(name, input, options.abortSignal, sandboxCfg, options.workDir, agentScope, onStream, options.selfPeer);
108332
108470
  const locks = name === "bash" ? (0, bashClassify_1.bashNeedsRepoLock)(String(input.command ?? "")) ? [REPO_STATE_LOCK] : [] : lockPathsFor(name, input, options.workDir);
108333
108471
  const dedupeStub = name === "read_file" ? readDedupe.check(input, messages) : null;
108472
+ const prefetched = !dedupeStub && prefetchSafe ? prefetcher.take(id, name, input) : null;
108334
108473
  if (dedupeStub) {
108335
108474
  result = { output: dedupeStub };
108475
+ } else if (prefetched) {
108476
+ result = await prefetched;
108477
+ if (name === "read_file" && result.error === void 0 && result.output?.startsWith("[File:")) {
108478
+ readDedupe.record(input, id);
108479
+ }
108336
108480
  } else {
108337
108481
  result = locks.length ? await (0, crossProcessLock_1.withCrossProcessLocks)(locks, () => withFileLocks(locks, run), options.workDir) : await run();
108338
108482
  if (name === "read_file" && result.error === void 0 && result.output?.startsWith("[File:")) {
@@ -156640,12 +156784,24 @@ function collectEnv(workDir) {
156640
156784
  platform: `${os5.platform()} ${os5.release()}`,
156641
156785
  shell: process.env.SHELL ?? process.env.ComSpec ?? "unknown",
156642
156786
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
156787
+ ...collectGitEnv(workDir)
156788
+ };
156789
+ }
156790
+ function collectGitEnv(workDir) {
156791
+ return {
156643
156792
  gitBranch: tryExec("git branch --show-current", workDir),
156644
156793
  gitStatus: tryExec("git status --short", workDir),
156645
156794
  gitDiff: tryExec("git diff --stat HEAD", workDir),
156646
156795
  recentCommits: tryExec("git log --oneline -5", workDir)
156647
156796
  };
156648
156797
  }
156798
+ function refreshEnvForTurn(env4, workDir) {
156799
+ return {
156800
+ ...env4,
156801
+ date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
156802
+ ...collectGitEnv(workDir)
156803
+ };
156804
+ }
156649
156805
  var NEXRALL_MD_CANDIDATES = [
156650
156806
  "nexrall.md",
156651
156807
  "NEXRALL.md",
@@ -156878,7 +157034,7 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env4, nexrall
156878
157034
  const result = await (0, import_code_core5.runAgentLoop)(messages, {
156879
157035
  workDir,
156880
157036
  model: modelAlias,
156881
- env: env4,
157037
+ env: refreshEnvForTurn(env4, workDir),
156882
157038
  nexrallMd,
156883
157039
  mode,
156884
157040
  // `/mode plan` previously only hinted to the backend — the footer said "plan
@@ -157087,7 +157243,7 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env4
157087
157243
  const result = await (0, import_code_core5.runAgentLoop)(messages, {
157088
157244
  workDir,
157089
157245
  model: modelAlias,
157090
- env: env4,
157246
+ env: refreshEnvForTurn(env4, workDir),
157091
157247
  nexrallMd,
157092
157248
  mode,
157093
157249
  planMode: mode === "plan",
@@ -157403,8 +157559,8 @@ Resume it with: nex --worktree ${worktreeState.name}
157403
157559
  startRowCount();
157404
157560
  }
157405
157561
  if (options.showBanner !== false && !headless) {
157406
- const branch = tryExec("git branch --show-current", workDir);
157407
- const gitStatus = tryExec("git status --short", workDir);
157562
+ const branch = env4.gitBranch;
157563
+ const gitStatus = env4.gitStatus;
157408
157564
  const changedLines = gitStatus ? gitStatus.split("\n").filter(Boolean).length : 0;
157409
157565
  const tips = ["Run /init to create a nexrall.md file with instructions for Nexrall Code."];
157410
157566
  if (branch) {
@@ -157472,11 +157628,8 @@ Resume it with: nex --worktree ${worktreeState.name}
157472
157628
  const compacted = await (0, import_code_core5.compactMessagesForResume)(messages, {
157473
157629
  workDir,
157474
157630
  model: modelAlias,
157475
- clientType: "cli",
157476
- onNotice: (text) => {
157477
- if (!headless)
157478
- console.log(source_default.dim(text.trim()));
157479
- }
157631
+ clientType: "cli"
157632
+ // Compaction is silent housekeeping — no "♻️ Auto-compacted …" line.
157480
157633
  });
157481
157634
  } catch {
157482
157635
  }
@@ -157709,8 +157862,8 @@ ${text}` : "");
157709
157862
  await (0, import_code_core5.compactMessagesForResume)(messages, {
157710
157863
  workDir,
157711
157864
  model: modelAlias,
157712
- clientType: "cli",
157713
- onNotice: (text) => console.log(source_default.dim(text.trim()))
157865
+ clientType: "cli"
157866
+ // Silent housekeeping — no "♻️ Auto-compacted …" line.
157714
157867
  });
157715
157868
  } catch {
157716
157869
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexrall-code",
3
- "version": "0.5.114",
3
+ "version": "0.5.115",
4
4
  "description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
5
5
  "keywords": [
6
6
  "ai",