nexrall-code 0.5.53 → 0.5.54

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 +186 -10
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -9982,7 +9982,7 @@ var require_client = __commonJS({
9982
9982
  }
9983
9983
  var MAX_TOTAL_ATTEMPTS = (MAX_RETRIES + 1) * (MAX_RETRIES + 1);
9984
9984
  async function streamChat(messages, options, onEvent) {
9985
- const { model, env: env3, editorContext, nexrallMd, mode, effort, clientType, abortSignal, extraTools, agents, skills, allowRestartAfterRender } = options;
9985
+ const { model, env: env3, editorContext, nexrallMd, mode, effort, clientType, abortSignal, extraTools, agents, skills, allowRestartAfterRender, canWriteSharedMemory, hasOwnAgentStore } = options;
9986
9986
  let turnId = (0, crypto_1.randomUUID)();
9987
9987
  const controller = new AbortController();
9988
9988
  if (abortSignal?.aborted)
@@ -10023,7 +10023,7 @@ var require_client = __commonJS({
10023
10023
  {
10024
10024
  method: "POST",
10025
10025
  headers: authHeaders(),
10026
- body: JSON.stringify({ messages, model, env: env3, editorContext, nexrallMd, mode, effort, clientType, extraTools, agents, skills, turnId }),
10026
+ body: JSON.stringify({ messages, model, env: env3, editorContext, nexrallMd, mode, effort, clientType, extraTools, agents, skills, turnId, canWriteSharedMemory, hasOwnAgentStore }),
10027
10027
  signal: controller.signal
10028
10028
  }
10029
10029
  ];
@@ -11012,13 +11012,18 @@ var require_memory = __commonJS({
11012
11012
  };
11013
11013
  }();
11014
11014
  Object.defineProperty(exports, "__esModule", { value: true });
11015
- exports.MEMORY_HARD_CAP_BYTES = exports.MEMORY_COMPACT_TRIGGER_BYTES = exports.MEMORY_MAX_BYTES = exports.MEMORY_ENTRY_MAX_CHARS = void 0;
11015
+ exports.AGENT_MEMORY_MAX_BYTES = exports.AGENT_MEMORY_INJECT_MAX = exports.MEMORY_HARD_CAP_BYTES = exports.MEMORY_COMPACT_TRIGGER_BYTES = exports.MEMORY_MAX_BYTES = exports.MEMORY_ENTRY_MAX_CHARS = void 0;
11016
11016
  exports.memoryFilePath = memoryFilePath;
11017
11017
  exports.writeMemory = writeMemory;
11018
11018
  exports.readMemory = readMemory2;
11019
11019
  exports.readAllMemory = readAllMemory2;
11020
11020
  exports.clearMemory = clearMemory2;
11021
11021
  exports.memoryStats = memoryStats2;
11022
+ exports.isSafeAgentName = isSafeAgentName;
11023
+ exports.agentMemoryPath = agentMemoryPath;
11024
+ exports.readAgentMemory = readAgentMemory;
11025
+ exports.writeAgentMemory = writeAgentMemory;
11026
+ exports.agentMemoryPreamble = agentMemoryPreamble;
11022
11027
  exports.compactMemoryIfNeeded = compactMemoryIfNeeded2;
11023
11028
  var fs9 = __importStar(__require("fs"));
11024
11029
  var os6 = __importStar(__require("os"));
@@ -11127,6 +11132,71 @@ ${projMem}`);
11127
11132
  const entries = content.split("\n").filter((l) => /^-\s\[\d{4}-\d{2}-\d{2}\]/.test(l)).length;
11128
11133
  return { file, bytes: Buffer.byteLength(content, "utf-8"), entries };
11129
11134
  }
11135
+ exports.AGENT_MEMORY_INJECT_MAX = 8e3;
11136
+ exports.AGENT_MEMORY_MAX_BYTES = 24e3;
11137
+ function isSafeAgentName(name) {
11138
+ return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name) && !name.includes("..");
11139
+ }
11140
+ function agentMemoryPath(agentName, scope, workDir) {
11141
+ if (!isSafeAgentName(agentName))
11142
+ return null;
11143
+ const file = `${agentName}.md`;
11144
+ let dir;
11145
+ if (scope === "user") {
11146
+ dir = path6.join(MEMORY_ROOT, "agent-memory");
11147
+ } else {
11148
+ if (!workDir)
11149
+ return null;
11150
+ dir = path6.join(path6.resolve(workDir), ".nexrall", scope === "local" ? "agent-memory-local" : "agent-memory");
11151
+ }
11152
+ const full = path6.join(dir, file);
11153
+ const rel = path6.relative(dir, full);
11154
+ if (rel.startsWith("..") || path6.isAbsolute(rel))
11155
+ return null;
11156
+ return full;
11157
+ }
11158
+ function readAgentMemory(agentName, scope, workDir) {
11159
+ const file = agentMemoryPath(agentName, scope, workDir);
11160
+ if (!file)
11161
+ return "";
11162
+ return readMemoryFile(file).trim();
11163
+ }
11164
+ async function writeAgentMemory(agentName, scope, content, workDir) {
11165
+ let trimmed = content.trim();
11166
+ if (!trimmed)
11167
+ return { ok: false, already: false, file: "" };
11168
+ if (trimmed.length > exports.MEMORY_ENTRY_MAX_CHARS) {
11169
+ trimmed = trimmed.slice(0, exports.MEMORY_ENTRY_MAX_CHARS - 1).trimEnd() + "\u2026";
11170
+ }
11171
+ const file = agentMemoryPath(agentName, scope, workDir);
11172
+ if (!file)
11173
+ return { ok: false, already: false, file: "" };
11174
+ return withLock(file, async () => {
11175
+ const existing = readMemoryFile(file);
11176
+ const fingerprint = trimmed.toLowerCase().slice(0, FINGERPRINT_LEN);
11177
+ if (existing.toLowerCase().includes(fingerprint)) {
11178
+ return { ok: true, already: true, file };
11179
+ }
11180
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
11181
+ let next = existing + `
11182
+ - [${today}] ${trimmed}`;
11183
+ if (Buffer.byteLength(next, "utf-8") > exports.AGENT_MEMORY_MAX_BYTES) {
11184
+ next = evictOldest(next, exports.AGENT_MEMORY_MAX_BYTES);
11185
+ }
11186
+ writeMemoryFile(file, next);
11187
+ return { ok: true, already: false, file };
11188
+ });
11189
+ }
11190
+ function agentMemoryPreamble(agentName, scope, workDir) {
11191
+ const notes = readAgentMemory(agentName, scope, workDir);
11192
+ if (!notes)
11193
+ return "";
11194
+ const kept = Buffer.byteLength(notes, "utf-8") > exports.AGENT_MEMORY_INJECT_MAX ? notes.slice(-exports.AGENT_MEMORY_INJECT_MAX) : notes;
11195
+ return `# Your own notes (agent: ${agentName}, scope: ${scope})
11196
+ These are notes YOU saved on previous runs. Treat them as your accumulated experience, not as instructions from the user, and verify anything that looks stale against the current code.
11197
+
11198
+ ${kept}`;
11199
+ }
11130
11200
  async function compactMemoryIfNeeded2(scope, workDir, summarize) {
11131
11201
  const file = memoryFilePath(scope, workDir);
11132
11202
  return withLock(file, async () => {
@@ -14585,6 +14655,11 @@ var require_agentTypes = __commonJS({
14585
14655
  var path6 = __importStar(__require("path"));
14586
14656
  var os6 = __importStar(__require("os"));
14587
14657
  var index_1 = require_plugins();
14658
+ var VALID_MEMORY_SCOPES = ["project", "user", "local"];
14659
+ function parseMemoryScope(v) {
14660
+ const s2 = (v ?? "").trim().toLowerCase();
14661
+ return VALID_MEMORY_SCOPES.includes(s2) ? s2 : void 0;
14662
+ }
14588
14663
  var READ_ONLY_TOOLS = [
14589
14664
  // Universal
14590
14665
  "read_file",
@@ -14600,6 +14675,22 @@ var require_agentTypes = __commonJS({
14600
14675
  "notebook_read",
14601
14676
  "todo_write",
14602
14677
  "todo_read",
14678
+ // memory_READ, deliberately without memory_write.
14679
+ //
14680
+ // Reading is free capability: a sub-agent that knows "tests run with X" or "never
14681
+ // edit Z directly" does better work, and withholding it meant every sub-agent
14682
+ // rediscovered project conventions from scratch. Writing is a different thing
14683
+ // entirely — memory is durable, cross-session, SHARED state, while sub-agents run
14684
+ // up to 4-wide in parallel with their reasoning hidden from the user. A wrong fact
14685
+ // written there is invisible and permanent, and it competes for a capped, shared
14686
+ // budget that gets periodically condensed.
14687
+ //
14688
+ // So the parent owns writes: a sub-agent that learns something durable says so in
14689
+ // its report, and the main agent — the one actually talking to the user — decides.
14690
+ // A sub-agent that needs its own persistent notes gets the per-agent store instead
14691
+ // (see `memory:` in the frontmatter), which is scoped to itself and cannot pollute
14692
+ // the shared file.
14693
+ "memory_read",
14603
14694
  // Skills are reusable prompt playbooks, and loop.ts advertises the skills
14604
14695
  // catalogue to sub-agents at EVERY depth — so withholding the tool that loads
14605
14696
  // one meant showing every sub-agent a menu it could not order from.
@@ -14832,6 +14923,9 @@ var require_agentTypes = __commonJS({
14832
14923
  "stock_photo",
14833
14924
  "open_in_browser",
14834
14925
  "task",
14926
+ // Granted by a `memory:` scope rather than listed in a `tools:` line, but a user may
14927
+ // still name it explicitly — so it must validate rather than be flagged as a typo.
14928
+ "agent_memory_write",
14835
14929
  "get_symbols",
14836
14930
  "get_workspace_symbols",
14837
14931
  "find_references",
@@ -14845,7 +14939,8 @@ var require_agentTypes = __commonJS({
14845
14939
  "tools",
14846
14940
  "model",
14847
14941
  "test_files_only",
14848
- "testfilesonly"
14942
+ "testfilesonly",
14943
+ "memory"
14849
14944
  ]);
14850
14945
  var VALID_MODELS = ["turbo", "pro", "ultra"];
14851
14946
  function parseFrontmatter(raw) {
@@ -14918,6 +15013,13 @@ var require_agentTypes = __commonJS({
14918
15013
  message: "has no `description:` \u2014 that text is the ONLY thing the model uses to decide when to delegate to this agent, so it will rarely be picked."
14919
15014
  });
14920
15015
  }
15016
+ if (meta.memory !== void 0 && parseMemoryScope(meta.memory) === void 0) {
15017
+ warnings.push({
15018
+ file: full,
15019
+ agent: name,
15020
+ message: `has memory: "${meta.memory}", which is not a valid scope \u2014 use one of ${VALID_MEMORY_SCOPES.join(", ")}, or omit the line to give this agent no persistent notes.`
15021
+ });
15022
+ }
14921
15023
  if (meta.model !== void 0 && parseModel(meta.model) === void 0) {
14922
15024
  warnings.push({
14923
15025
  file: full,
@@ -14960,7 +15062,13 @@ var require_agentTypes = __commonJS({
14960
15062
  // `testFilesOnly`) lets anyone build a test-writing agent that genuinely
14961
15063
  // cannot touch production source, rather than only the builtin getting
14962
15064
  // that guarantee.
14963
- ...parseBool(meta.test_files_only ?? meta.testfilesonly) ? { testFilesOnly: true } : {}
15065
+ ...parseBool(meta.test_files_only ?? meta.testfilesonly) ? { testFilesOnly: true } : {},
15066
+ // The `ok &&` is defensive, not load-bearing: parseFrontmatter already returns an
15067
+ // EMPTY meta when it cannot find a `---` block, so `meta.memory` is undefined on
15068
+ // that path regardless. It stays because the guarantee we want — an unreadable
15069
+ // definition never receives a writable store — should survive parseFrontmatter
15070
+ // being changed to salvage partial metadata, which is a plausible future edit.
15071
+ ...ok && parseMemoryScope(meta.memory) ? { memory: parseMemoryScope(meta.memory) } : {}
14964
15072
  });
14965
15073
  }
14966
15074
  }
@@ -15763,8 +15871,9 @@ var require_loop = __commonJS({
15763
15871
  };
15764
15872
  }();
15765
15873
  Object.defineProperty(exports, "__esModule", { value: true });
15766
- exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = exports.ToolNotAllowedError = exports._stallLimits = void 0;
15874
+ exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = exports.ToolNotAllowedError = exports.AGENT_MEMORY_TOOL_SCHEMA = exports.AGENT_MEMORY_TOOL = exports._stallLimits = void 0;
15767
15875
  exports.errorRoundSignature = errorRoundSignature;
15876
+ exports.executeAgentMemoryWrite = executeAgentMemoryWrite;
15768
15877
  exports.stopReasonNotice = stopReasonNotice;
15769
15878
  exports.resolveMaxIterations = resolveMaxIterations;
15770
15879
  exports.createLimiter = createLimiter;
@@ -15903,6 +16012,37 @@ var require_loop = __commonJS({
15903
16012
  return errored.map(({ name, error }) => `${name}:${String(error).slice(0, 200)}`).sort().join("|");
15904
16013
  }
15905
16014
  exports._stallLimits = { STALL_LIMIT, REPEAT_STALL_LIMIT };
16015
+ exports.AGENT_MEMORY_TOOL = "agent_memory_write";
16016
+ exports.AGENT_MEMORY_TOOL_SCHEMA = {
16017
+ name: exports.AGENT_MEMORY_TOOL,
16018
+ description: "Save a durable note to YOUR OWN persistent notes, which are injected into your prompt on future runs. Use this for lessons that will still be true next time \u2014 a convention this repo follows, a recurring bug pattern, a command that works, a dead end not worth retrying. These notes are PRIVATE to you: they are NOT shown to the user and NOT read by the main agent, so anything the user or the main agent needs to know must still go in your final message. One self-contained fact per call, a sentence or two. Do not save transient task details.",
16019
+ input_schema: {
16020
+ type: "object",
16021
+ properties: {
16022
+ content: { type: "string", description: "The single fact to remember, 1-2 sentences." }
16023
+ },
16024
+ required: ["content"]
16025
+ }
16026
+ };
16027
+ async function executeAgentMemoryWrite(input, binding, workDir) {
16028
+ if (!binding) {
16029
+ return {
16030
+ error: `${exports.AGENT_MEMORY_TOOL} is only available to a sub-agent whose definition declares a \`memory:\` scope. Put anything worth remembering in your final message instead.`
16031
+ };
16032
+ }
16033
+ const content = typeof input.content === "string" ? input.content.trim() : "";
16034
+ if (!content)
16035
+ return { error: `${exports.AGENT_MEMORY_TOOL} requires a non-empty \`content\` string.` };
16036
+ const res = await (0, memory_1.writeAgentMemory)(binding.agentName, binding.scope, content, workDir);
16037
+ if (!res.ok) {
16038
+ return {
16039
+ error: `Could not save to the "${binding.agentName}" agent's ${binding.scope} notes. Either this scope needs a project directory (use \`memory: user\` for a store that works anywhere) or the agent name is not usable as a filename.`
16040
+ };
16041
+ }
16042
+ return {
16043
+ output: res.already ? "Already saved (a near-identical note exists) \u2014 nothing added." : `Saved to your ${binding.scope} notes. It will be in your prompt on your next run.`
16044
+ };
16045
+ }
15906
16046
  function stopReasonNotice(reason, ctx = {}) {
15907
16047
  switch (reason) {
15908
16048
  case "clean":
@@ -16182,14 +16322,25 @@ ${tail}`;
16182
16322
  If you just created .nexrall/agents/` + requestedType + ".md, make sure the write finished in an EARLIER tool call than this one \u2014 a file written in the same batch may not be on disk yet."
16183
16323
  };
16184
16324
  }
16325
+ const memoryScope = agent?.memory;
16326
+ const agentMemoryNotes = agent && memoryScope ? (0, memory_1.agentMemoryPreamble)(agent.name, memoryScope, options.workDir) : "";
16185
16327
  const subNexrallMd = agent ? `# Sub-agent role: ${agent.name}
16186
- ${agent.prompt}` + (options.nexrallMd ? `
16328
+ ${agent.prompt}` + (agentMemoryNotes ? `
16329
+
16330
+ ---
16331
+
16332
+ ${agentMemoryNotes}` : "") + (options.nexrallMd ? `
16187
16333
 
16188
16334
  ---
16189
16335
 
16190
16336
  ${options.nexrallMd}` : "") : options.nexrallMd;
16191
16337
  const allowed = agent?.tools ? new Set(agent.tools) : null;
16338
+ if (allowed && memoryScope)
16339
+ allowed.add(exports.AGENT_MEMORY_TOOL);
16192
16340
  const gatedPermission = async (req) => {
16341
+ if (req.tool === exports.AGENT_MEMORY_TOOL && !(agent && memoryScope)) {
16342
+ throw new ToolNotAllowedError(`\`${exports.AGENT_MEMORY_TOOL}\` is only available to a sub-agent whose definition declares a \`memory:\` scope (project, user or local). Report anything worth remembering in your final message instead \u2014 the main agent decides what to persist.`);
16343
+ }
16193
16344
  if (allowed && !allowed.has(req.tool)) {
16194
16345
  throw new ToolNotAllowedError(`The "${agent.name}" sub-agent is not allowed to use \`${req.tool}\` \u2014 it is not in that agent's tool allowlist. This is a restriction of the agent definition, NOT a user decision: do not ask for approval, use one of the tools you do have, or report back that the task needs a different agent.`);
16195
16346
  }
@@ -16213,6 +16364,11 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
16213
16364
  _depth: depth + 1,
16214
16365
  _agentScope: `sub_${++_subTaskCounter}`,
16215
16366
  // 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 } } : {},
16369
+ // The same set `gatedPermission` enforces above, so prompt and permission agree
16370
+ // by construction instead of by two people remembering to update both.
16371
+ ...allowed ? { _allowedTools: allowed } : {},
16216
16372
  editorContext: null,
16217
16373
  // fresh isolated context for sub-agent
16218
16374
  model: agent?.model ?? options.model,
@@ -16805,7 +16961,22 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
16805
16961
  nexrallMd: planAwareNexrallMd,
16806
16962
  clientType: options.clientType,
16807
16963
  abortSignal: options.abortSignal,
16808
- extraTools: options.mcpManager?.getAnthropicTools(),
16964
+ // MCP tools, plus the per-agent memory tool when (and only when) this run
16965
+ // is a sub-agent that declared a `memory:` scope. Declaring it through
16966
+ // extraTools rather than the backend's static catalogue keeps it invisible
16967
+ // to every other run: a tool the model cannot see is one it cannot try,
16968
+ // which is better than advertising it everywhere and refusing it at the gate.
16969
+ extraTools: [
16970
+ ...options.mcpManager?.getAnthropicTools() ?? [],
16971
+ ...options._agentMemory ? [exports.AGENT_MEMORY_TOOL_SCHEMA] : []
16972
+ ],
16973
+ // Derived from the run's ACTUAL allowlist rather than asserted separately,
16974
+ // so the prompt's memory instructions cannot drift from what is permitted.
16975
+ // That drift is the bug being fixed: sub-agents were told they MUST call
16976
+ // memory_write, which no sub-agent allowlist contains.
16977
+ canWriteSharedMemory: options._allowedTools ? options._allowedTools.has("memory_write") : true,
16978
+ // no allowlist = main agent = may write
16979
+ hasOwnAgentStore: !!options._agentMemory,
16809
16980
  agents: agentsCatalogue || void 0,
16810
16981
  skills: skillsCatalogue || void 0,
16811
16982
  // Only allow a post-render restart when the caller actually implements the
@@ -16956,6 +17127,11 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
16956
17127
  result = { error: `Blocked by PreToolUse hook: ${pre.reason}` };
16957
17128
  } else {
16958
17129
  try {
17130
+ if (name === exports.AGENT_MEMORY_TOOL) {
17131
+ result = await executeAgentMemoryWrite(input, options._agentMemory, options.workDir);
17132
+ options.onToolResult(name, result);
17133
+ return { block: { ...block, id }, result };
17134
+ }
16959
17135
  const external = options.executeExternalTool ? await options.executeExternalTool(name, input) : null;
16960
17136
  if (external !== null && external !== void 0) {
16961
17137
  result = external;
@@ -64038,7 +64214,7 @@ var InkReadlineAdapter = class extends EventEmitter3 {
64038
64214
  };
64039
64215
 
64040
64216
  // src/commands/chat.ts
64041
- var CLI_VERSION = "0.5.53";
64217
+ var CLI_VERSION = "0.5.54";
64042
64218
  var MODEL_LABELS = {
64043
64219
  turbo: "Nexrall Turbo",
64044
64220
  pro: "Nexrall Pro",
@@ -65705,7 +65881,7 @@ function pluginSourceRemoveCommand(name, opts) {
65705
65881
 
65706
65882
  // src/index.ts
65707
65883
  var program2 = new Command();
65708
- program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.53").enablePositionalOptions();
65884
+ program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.54").enablePositionalOptions();
65709
65885
  program2.command("auth").description("Login to your Nexrall account").action(authCommand);
65710
65886
  program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
65711
65887
  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.53",
3
+ "version": "0.5.54",
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.28"
44
+ "@nexrall/code-core": "1.4.29"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@aws-sdk/client-s3": "^3.600.0",