nexrall-code 0.5.53 → 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.
- package/dist/index.js +337 -26
- 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.
|
|
@@ -14627,6 +14718,29 @@ var require_agentTypes = __commonJS({
|
|
|
14627
14718
|
"notebook_edit"
|
|
14628
14719
|
];
|
|
14629
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
|
+
},
|
|
14630
14744
|
{
|
|
14631
14745
|
name: "reviewer",
|
|
14632
14746
|
description: "Read-only code reviewer \u2014 finds correctness bugs, edge cases, and security issues in a diff or file set. Cannot modify files.",
|
|
@@ -14689,6 +14803,9 @@ var require_agentTypes = __commonJS({
|
|
|
14689
14803
|
// frontier model, and this is the agent most likely to be spawned in bulk.
|
|
14690
14804
|
{
|
|
14691
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,
|
|
14692
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.",
|
|
14693
14810
|
tools: READ_ONLY_TOOLS,
|
|
14694
14811
|
model: "turbo",
|
|
@@ -14832,6 +14949,9 @@ var require_agentTypes = __commonJS({
|
|
|
14832
14949
|
"stock_photo",
|
|
14833
14950
|
"open_in_browser",
|
|
14834
14951
|
"task",
|
|
14952
|
+
// Granted by a `memory:` scope rather than listed in a `tools:` line, but a user may
|
|
14953
|
+
// still name it explicitly — so it must validate rather than be flagged as a typo.
|
|
14954
|
+
"agent_memory_write",
|
|
14835
14955
|
"get_symbols",
|
|
14836
14956
|
"get_workspace_symbols",
|
|
14837
14957
|
"find_references",
|
|
@@ -14845,7 +14965,8 @@ var require_agentTypes = __commonJS({
|
|
|
14845
14965
|
"tools",
|
|
14846
14966
|
"model",
|
|
14847
14967
|
"test_files_only",
|
|
14848
|
-
"testfilesonly"
|
|
14968
|
+
"testfilesonly",
|
|
14969
|
+
"memory"
|
|
14849
14970
|
]);
|
|
14850
14971
|
var VALID_MODELS = ["turbo", "pro", "ultra"];
|
|
14851
14972
|
function parseFrontmatter(raw) {
|
|
@@ -14918,6 +15039,13 @@ var require_agentTypes = __commonJS({
|
|
|
14918
15039
|
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
15040
|
});
|
|
14920
15041
|
}
|
|
15042
|
+
if (meta.memory !== void 0 && parseMemoryScope(meta.memory) === void 0) {
|
|
15043
|
+
warnings.push({
|
|
15044
|
+
file: full,
|
|
15045
|
+
agent: name,
|
|
15046
|
+
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.`
|
|
15047
|
+
});
|
|
15048
|
+
}
|
|
14921
15049
|
if (meta.model !== void 0 && parseModel(meta.model) === void 0) {
|
|
14922
15050
|
warnings.push({
|
|
14923
15051
|
file: full,
|
|
@@ -14960,7 +15088,13 @@ var require_agentTypes = __commonJS({
|
|
|
14960
15088
|
// `testFilesOnly`) lets anyone build a test-writing agent that genuinely
|
|
14961
15089
|
// cannot touch production source, rather than only the builtin getting
|
|
14962
15090
|
// that guarantee.
|
|
14963
|
-
...parseBool(meta.test_files_only ?? meta.testfilesonly) ? { testFilesOnly: true } : {}
|
|
15091
|
+
...parseBool(meta.test_files_only ?? meta.testfilesonly) ? { testFilesOnly: true } : {},
|
|
15092
|
+
// The `ok &&` is defensive, not load-bearing: parseFrontmatter already returns an
|
|
15093
|
+
// EMPTY meta when it cannot find a `---` block, so `meta.memory` is undefined on
|
|
15094
|
+
// that path regardless. It stays because the guarantee we want — an unreadable
|
|
15095
|
+
// definition never receives a writable store — should survive parseFrontmatter
|
|
15096
|
+
// being changed to salvage partial metadata, which is a plausible future edit.
|
|
15097
|
+
...ok && parseMemoryScope(meta.memory) ? { memory: parseMemoryScope(meta.memory) } : {}
|
|
14964
15098
|
});
|
|
14965
15099
|
}
|
|
14966
15100
|
}
|
|
@@ -15763,14 +15897,17 @@ var require_loop = __commonJS({
|
|
|
15763
15897
|
};
|
|
15764
15898
|
}();
|
|
15765
15899
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15766
|
-
exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = exports.ToolNotAllowedError = exports._stallLimits = void 0;
|
|
15900
|
+
exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = exports.ToolNotAllowedError = exports.AGENT_MEMORY_TOOL_SCHEMA = exports.AGENT_MEMORY_TOOL = exports._stallLimits = void 0;
|
|
15767
15901
|
exports.errorRoundSignature = errorRoundSignature;
|
|
15902
|
+
exports.executeAgentMemoryWrite = executeAgentMemoryWrite;
|
|
15768
15903
|
exports.stopReasonNotice = stopReasonNotice;
|
|
15769
15904
|
exports.resolveMaxIterations = resolveMaxIterations;
|
|
15770
15905
|
exports.createLimiter = createLimiter;
|
|
15906
|
+
exports.resolveSubtaskTimeoutMs = resolveSubtaskTimeoutMs;
|
|
15771
15907
|
exports.extractSubTaskText = extractSubTaskText;
|
|
15772
15908
|
exports.capSubTaskText = capSubTaskText;
|
|
15773
15909
|
exports.summariseSubTaskProgress = summariseSubTaskProgress;
|
|
15910
|
+
exports.lastToolResults = lastToolResults;
|
|
15774
15911
|
exports.contextWindowFor = contextWindowFor2;
|
|
15775
15912
|
exports.compactionThresholds = compactionThresholds2;
|
|
15776
15913
|
exports.estimateBodyBytes = estimateBodyBytes2;
|
|
@@ -15903,6 +16040,37 @@ var require_loop = __commonJS({
|
|
|
15903
16040
|
return errored.map(({ name, error }) => `${name}:${String(error).slice(0, 200)}`).sort().join("|");
|
|
15904
16041
|
}
|
|
15905
16042
|
exports._stallLimits = { STALL_LIMIT, REPEAT_STALL_LIMIT };
|
|
16043
|
+
exports.AGENT_MEMORY_TOOL = "agent_memory_write";
|
|
16044
|
+
exports.AGENT_MEMORY_TOOL_SCHEMA = {
|
|
16045
|
+
name: exports.AGENT_MEMORY_TOOL,
|
|
16046
|
+
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.",
|
|
16047
|
+
input_schema: {
|
|
16048
|
+
type: "object",
|
|
16049
|
+
properties: {
|
|
16050
|
+
content: { type: "string", description: "The single fact to remember, 1-2 sentences." }
|
|
16051
|
+
},
|
|
16052
|
+
required: ["content"]
|
|
16053
|
+
}
|
|
16054
|
+
};
|
|
16055
|
+
async function executeAgentMemoryWrite(input, binding, workDir) {
|
|
16056
|
+
if (!binding) {
|
|
16057
|
+
return {
|
|
16058
|
+
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.`
|
|
16059
|
+
};
|
|
16060
|
+
}
|
|
16061
|
+
const content = typeof input.content === "string" ? input.content.trim() : "";
|
|
16062
|
+
if (!content)
|
|
16063
|
+
return { error: `${exports.AGENT_MEMORY_TOOL} requires a non-empty \`content\` string.` };
|
|
16064
|
+
const res = await (0, memory_1.writeAgentMemory)(binding.agentName, binding.scope, content, workDir);
|
|
16065
|
+
if (!res.ok) {
|
|
16066
|
+
return {
|
|
16067
|
+
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.`
|
|
16068
|
+
};
|
|
16069
|
+
}
|
|
16070
|
+
return {
|
|
16071
|
+
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.`
|
|
16072
|
+
};
|
|
16073
|
+
}
|
|
15906
16074
|
function stopReasonNotice(reason, ctx = {}) {
|
|
15907
16075
|
switch (reason) {
|
|
15908
16076
|
case "clean":
|
|
@@ -16082,7 +16250,17 @@ ${ctx.repeatError}
|
|
|
16082
16250
|
}
|
|
16083
16251
|
var MAX_TASK_DEPTH = 1;
|
|
16084
16252
|
var _subTaskCounter = 0;
|
|
16085
|
-
var
|
|
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
|
+
}
|
|
16086
16264
|
var SUBTASK_MAX = 48e3;
|
|
16087
16265
|
var ToolNotAllowedError = class extends Error {
|
|
16088
16266
|
constructor(message) {
|
|
@@ -16139,11 +16317,59 @@ ${tail}`;
|
|
|
16139
16317
|
}
|
|
16140
16318
|
if (toolNames.length === 0)
|
|
16141
16319
|
return "";
|
|
16320
|
+
const recentFindings = lastToolResults(messages, SALVAGE_RESULT_COUNT, SALVAGE_RESULT_CHARS);
|
|
16142
16321
|
const counts = /* @__PURE__ */ new Map();
|
|
16143
16322
|
for (const n of toolNames)
|
|
16144
16323
|
counts.set(n, (counts.get(n) ?? 0) + 1);
|
|
16145
16324
|
const inventory = [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([name, n]) => n > 1 ? `${name} \xD7${n}` : name).join(", ");
|
|
16146
|
-
|
|
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 "";
|
|
16147
16373
|
}
|
|
16148
16374
|
async function runSubTask(input, options, agentTypes) {
|
|
16149
16375
|
const prompt2 = typeof input.prompt === "string" ? input.prompt.trim() : "";
|
|
@@ -16182,14 +16408,26 @@ ${tail}`;
|
|
|
16182
16408
|
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
16409
|
};
|
|
16184
16410
|
}
|
|
16411
|
+
const memoryScope = agent?.memory;
|
|
16412
|
+
const agentMemoryNotes = agent && memoryScope ? (0, memory_1.agentMemoryPreamble)(agent.name, memoryScope, options.workDir) : "";
|
|
16413
|
+
const inheritedMd = agent?.lightPrompt ? "" : options.nexrallMd ?? "";
|
|
16185
16414
|
const subNexrallMd = agent ? `# Sub-agent role: ${agent.name}
|
|
16186
|
-
${agent.prompt}` + (
|
|
16415
|
+
${agent.prompt}` + (agentMemoryNotes ? `
|
|
16187
16416
|
|
|
16188
16417
|
---
|
|
16189
16418
|
|
|
16190
|
-
${
|
|
16419
|
+
${agentMemoryNotes}` : "") + (inheritedMd ? `
|
|
16420
|
+
|
|
16421
|
+
---
|
|
16422
|
+
|
|
16423
|
+
${inheritedMd}` : "") : options.nexrallMd;
|
|
16191
16424
|
const allowed = agent?.tools ? new Set(agent.tools) : null;
|
|
16425
|
+
if (allowed && memoryScope)
|
|
16426
|
+
allowed.add(exports.AGENT_MEMORY_TOOL);
|
|
16192
16427
|
const gatedPermission = async (req) => {
|
|
16428
|
+
if (req.tool === exports.AGENT_MEMORY_TOOL && !(agent && memoryScope)) {
|
|
16429
|
+
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.`);
|
|
16430
|
+
}
|
|
16193
16431
|
if (allowed && !allowed.has(req.tool)) {
|
|
16194
16432
|
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
16433
|
}
|
|
@@ -16200,9 +16438,18 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16200
16438
|
};
|
|
16201
16439
|
const subMessages = resumed ? [...resumed.messages, { role: "user", content: [{ type: "text", text: prompt2 }] }] : [{ role: "user", content: [{ type: "text", text: prompt2 }] }];
|
|
16202
16440
|
const subAbort = { aborted: false };
|
|
16203
|
-
const
|
|
16204
|
-
|
|
16205
|
-
|
|
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);
|
|
16206
16453
|
const parentAbortPoll = setInterval(() => {
|
|
16207
16454
|
if (options.abortSignal?.aborted)
|
|
16208
16455
|
subAbort.aborted = true;
|
|
@@ -16213,6 +16460,27 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16213
16460
|
_depth: depth + 1,
|
|
16214
16461
|
_agentScope: `sub_${++_subTaskCounter}`,
|
|
16215
16462
|
// isolated todo store per sub-agent
|
|
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,
|
|
16481
|
+
// The same set `gatedPermission` enforces above, so prompt and permission agree
|
|
16482
|
+
// by construction instead of by two people remembering to update both.
|
|
16483
|
+
_allowedTools: allowed ?? void 0,
|
|
16216
16484
|
editorContext: null,
|
|
16217
16485
|
// fresh isolated context for sub-agent
|
|
16218
16486
|
model: agent?.model ?? options.model,
|
|
@@ -16244,25 +16512,48 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16244
16512
|
// Forward tool events with isSubTask=true so the UI can render a badge
|
|
16245
16513
|
// instead of prepending "[sub-task]" to the tool name (which caused double-prefix
|
|
16246
16514
|
// when the name was already labelled, and mixed display concerns into the data layer).
|
|
16247
|
-
|
|
16248
|
-
|
|
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
|
+
},
|
|
16249
16527
|
onToolStreamChunk: (n, c) => options.onToolStreamChunk?.(n, c, true),
|
|
16250
16528
|
// Forward thinking so the UI shows the indicator while sub-agent reasons
|
|
16251
|
-
|
|
16252
|
-
|
|
16253
|
-
|
|
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
|
+
}
|
|
16254
16544
|
});
|
|
16255
|
-
if (
|
|
16256
|
-
const mins = Math.round(
|
|
16545
|
+
if (stalled && !options.abortSignal?.aborted) {
|
|
16546
|
+
const mins = Math.round(subtaskTimeoutMs / 6e4);
|
|
16257
16547
|
const partial = capSubTaskText(extractSubTaskText(result, false));
|
|
16258
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);
|
|
16259
16550
|
const sections = [
|
|
16260
|
-
`Sub-task STOPPED after ${mins} minutes
|
|
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.`,
|
|
16261
16552
|
progress,
|
|
16262
16553
|
partial ? `Partial output before it was stopped:
|
|
16263
16554
|
|
|
16264
16555
|
${partial}` : "",
|
|
16265
|
-
|
|
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.`
|
|
16266
16557
|
].filter(Boolean);
|
|
16267
16558
|
return { error: sections.join("\n\n") };
|
|
16268
16559
|
}
|
|
@@ -16291,7 +16582,7 @@ ${partial}` : "",
|
|
|
16291
16582
|
}
|
|
16292
16583
|
return { error: `Sub-task failed: ${err.message}` };
|
|
16293
16584
|
} finally {
|
|
16294
|
-
|
|
16585
|
+
clearInterval(stallWatchdog);
|
|
16295
16586
|
clearInterval(parentAbortPoll);
|
|
16296
16587
|
}
|
|
16297
16588
|
}
|
|
@@ -16805,7 +17096,22 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16805
17096
|
nexrallMd: planAwareNexrallMd,
|
|
16806
17097
|
clientType: options.clientType,
|
|
16807
17098
|
abortSignal: options.abortSignal,
|
|
16808
|
-
|
|
17099
|
+
// MCP tools, plus the per-agent memory tool when (and only when) this run
|
|
17100
|
+
// is a sub-agent that declared a `memory:` scope. Declaring it through
|
|
17101
|
+
// extraTools rather than the backend's static catalogue keeps it invisible
|
|
17102
|
+
// to every other run: a tool the model cannot see is one it cannot try,
|
|
17103
|
+
// which is better than advertising it everywhere and refusing it at the gate.
|
|
17104
|
+
extraTools: [
|
|
17105
|
+
...options.mcpManager?.getAnthropicTools() ?? [],
|
|
17106
|
+
...options._agentMemory ? [exports.AGENT_MEMORY_TOOL_SCHEMA] : []
|
|
17107
|
+
],
|
|
17108
|
+
// Derived from the run's ACTUAL allowlist rather than asserted separately,
|
|
17109
|
+
// so the prompt's memory instructions cannot drift from what is permitted.
|
|
17110
|
+
// That drift is the bug being fixed: sub-agents were told they MUST call
|
|
17111
|
+
// memory_write, which no sub-agent allowlist contains.
|
|
17112
|
+
canWriteSharedMemory: options._allowedTools ? options._allowedTools.has("memory_write") : true,
|
|
17113
|
+
// no allowlist = main agent = may write
|
|
17114
|
+
hasOwnAgentStore: !!options._agentMemory,
|
|
16809
17115
|
agents: agentsCatalogue || void 0,
|
|
16810
17116
|
skills: skillsCatalogue || void 0,
|
|
16811
17117
|
// Only allow a post-render restart when the caller actually implements the
|
|
@@ -16956,6 +17262,11 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16956
17262
|
result = { error: `Blocked by PreToolUse hook: ${pre.reason}` };
|
|
16957
17263
|
} else {
|
|
16958
17264
|
try {
|
|
17265
|
+
if (name === exports.AGENT_MEMORY_TOOL) {
|
|
17266
|
+
result = await executeAgentMemoryWrite(input, options._agentMemory, options.workDir);
|
|
17267
|
+
options.onToolResult(name, result);
|
|
17268
|
+
return { block: { ...block, id }, result };
|
|
17269
|
+
}
|
|
16959
17270
|
const external = options.executeExternalTool ? await options.executeExternalTool(name, input) : null;
|
|
16960
17271
|
if (external !== null && external !== void 0) {
|
|
16961
17272
|
result = external;
|
|
@@ -64038,7 +64349,7 @@ var InkReadlineAdapter = class extends EventEmitter3 {
|
|
|
64038
64349
|
};
|
|
64039
64350
|
|
|
64040
64351
|
// src/commands/chat.ts
|
|
64041
|
-
var CLI_VERSION = "0.5.
|
|
64352
|
+
var CLI_VERSION = "0.5.55";
|
|
64042
64353
|
var MODEL_LABELS = {
|
|
64043
64354
|
turbo: "Nexrall Turbo",
|
|
64044
64355
|
pro: "Nexrall Pro",
|
|
@@ -65705,7 +66016,7 @@ function pluginSourceRemoveCommand(name, opts) {
|
|
|
65705
66016
|
|
|
65706
66017
|
// src/index.ts
|
|
65707
66018
|
var program2 = new Command();
|
|
65708
|
-
program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.
|
|
66019
|
+
program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.55").enablePositionalOptions();
|
|
65709
66020
|
program2.command("auth").description("Login to your Nexrall account").action(authCommand);
|
|
65710
66021
|
program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
|
|
65711
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.
|
|
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.
|
|
44
|
+
"@nexrall/code-core": "1.4.30"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@aws-sdk/client-s3": "^3.600.0",
|