nexrall-code 0.5.166 → 0.5.168

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 +518 -49
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11355,7 +11355,7 @@ var require_memory = __commonJS({
11355
11355
  }();
11356
11356
  Object.defineProperty(exports2, "__esModule", { value: true });
11357
11357
  exports2.AGENT_MEMORY_MAX_BYTES = exports2.AGENT_MEMORY_INJECT_MAX = exports2.MEMORY_ARCHIVE_MAX_BYTES = exports2.MEMORY_HARD_CAP_BYTES = exports2.MEMORY_COMPACT_TRIGGER_BYTES = exports2.MEMORY_MAX_BYTES = exports2.MEMORY_ENTRY_MAX_CHARS = void 0;
11358
- exports2.memoryFilePath = memoryFilePath;
11358
+ exports2.memoryFilePath = memoryFilePath2;
11359
11359
  exports2.writeMemory = writeMemory2;
11360
11360
  exports2.readMemory = readMemory2;
11361
11361
  exports2.readAllMemory = readAllMemory2;
@@ -11374,7 +11374,7 @@ var require_memory = __commonJS({
11374
11374
  var crypto9 = __importStar(__require("crypto"));
11375
11375
  var MEMORY_ROOT = process.env.NEXRALL_MEMORY_DIR || path28.join(os17.homedir(), ".nexrall", "memory");
11376
11376
  var GLOBAL_FILE = path28.join(MEMORY_ROOT, "global.md");
11377
- function memoryFilePath(scope, workDir) {
11377
+ function memoryFilePath2(scope, workDir) {
11378
11378
  if (scope === "global" || !workDir)
11379
11379
  return GLOBAL_FILE;
11380
11380
  const key = crypto9.createHash("sha1").update(path28.resolve(workDir)).digest("hex").slice(0, 16);
@@ -11468,7 +11468,7 @@ ${SUPERSEDED_HEADER}
11468
11468
  trimmed = trimmed.slice(0, budget - 1).trimEnd() + "\u2026";
11469
11469
  }
11470
11470
  const body = prefix2 && budget > 0 ? prefix2 + trimmed : trimmed;
11471
- const file = memoryFilePath(scope, workDir);
11471
+ const file = memoryFilePath2(scope, workDir);
11472
11472
  return withLock(file, async () => {
11473
11473
  const existing = readMemoryFile(file);
11474
11474
  let { active, archived } = parseSections(existing);
@@ -11503,7 +11503,7 @@ ${SUPERSEDED_HEADER}
11503
11503
  });
11504
11504
  }
11505
11505
  function readMemory2(scope, workDir, opts = {}) {
11506
- const file = memoryFilePath(scope, workDir);
11506
+ const file = memoryFilePath2(scope, workDir);
11507
11507
  const { active, archived } = parseSections(readMemoryFile(file));
11508
11508
  if (!opts.includeArchived)
11509
11509
  return active.length ? serializeSections(active, []).trim() : "";
@@ -11524,14 +11524,14 @@ ${projMem}`);
11524
11524
  return parts.join("\n\n");
11525
11525
  }
11526
11526
  function clearMemory2(scope, workDir) {
11527
- const file = memoryFilePath(scope, workDir);
11527
+ const file = memoryFilePath2(scope, workDir);
11528
11528
  try {
11529
11529
  fs28.rmSync(file, { force: true });
11530
11530
  } catch {
11531
11531
  }
11532
11532
  }
11533
11533
  function memoryStats2(scope, workDir) {
11534
- const file = memoryFilePath(scope, workDir);
11534
+ const file = memoryFilePath2(scope, workDir);
11535
11535
  const content = readMemoryFile(file);
11536
11536
  const { active, archived } = parseSections(content);
11537
11537
  return {
@@ -11545,7 +11545,7 @@ ${projMem}`);
11545
11545
  return reply.split("\n").map((l2) => l2.trimEnd()).filter((l2) => /^-\s\[\d{4}-\d{2}-\d{2}\]\s*\S/.test(l2));
11546
11546
  }
11547
11547
  async function compactMemoryIfNeeded2(scope, workDir, summarize) {
11548
- const file = memoryFilePath(scope, workDir);
11548
+ const file = memoryFilePath2(scope, workDir);
11549
11549
  return withLock(file, async () => {
11550
11550
  const content = readMemoryFile(file);
11551
11551
  if (Buffer.byteLength(content, "utf-8") <= exports2.MEMORY_COMPACT_TRIGGER_BYTES)
@@ -106910,6 +106910,9 @@ var require_loop = __commonJS({
106910
106910
  }();
106911
106911
  Object.defineProperty(exports2, "__esModule", { value: true });
106912
106912
  exports2.VERIFY_CMD_RE = exports2.WRITE_TOOL_NAMES = exports2.ToolNotAllowedError = exports2.AGENT_MEMORY_TOOL_SCHEMA = exports2.AGENT_MEMORY_TOOL = exports2._stallLimits = exports2._emptyTurnRetry = exports2.bashNeedsRepoLock = void 0;
106913
+ exports2.loadHooks = loadHooks;
106914
+ exports2.parsePromptHookReply = parsePromptHookReply;
106915
+ exports2.classifyStopFailure = classifyStopFailure;
106913
106916
  exports2.runLifecycleHooks = runLifecycleHooks;
106914
106917
  exports2.runPermissionRequestHooks = runPermissionRequestHooks2;
106915
106918
  exports2.fireNotificationHooks = fireNotificationHooks2;
@@ -107098,8 +107101,206 @@ var require_loop = __commonJS({
107098
107101
  async function runHookHandler(hook, payload, opts) {
107099
107102
  if (hook.type === "http")
107100
107103
  return httpHook(hook, payload, opts.timeoutMs);
107104
+ if (hook.type === "prompt" || hook.type === "agent") {
107105
+ return promptHook(hook, payload, { ...opts, envCtx: opts.hookEnv });
107106
+ }
107101
107107
  return spawnHook(String(hook.command ?? ""), { cwd: opts.cwd, timeout: opts.timeoutMs, input: payload, env: opts.env });
107102
107108
  }
107109
+ var HOOK_AGENT_TOOLS = [
107110
+ {
107111
+ name: "read_file",
107112
+ description: "Read a file from the working directory.",
107113
+ input_schema: {
107114
+ type: "object",
107115
+ properties: {
107116
+ path: { type: "string", description: "File path, relative to the working directory." },
107117
+ offset: { type: "number", description: "0-based line to start from." },
107118
+ limit: { type: "number", description: "Maximum number of lines." }
107119
+ },
107120
+ required: ["path"]
107121
+ }
107122
+ },
107123
+ {
107124
+ name: "search_files",
107125
+ description: "Search file contents (regex) or find files by name in the working directory.",
107126
+ input_schema: {
107127
+ type: "object",
107128
+ properties: {
107129
+ pattern: { type: "string", description: "Regex or literal string to search for." },
107130
+ path: { type: "string", description: "Directory to search in." },
107131
+ type: { type: "string", description: "files (paths only) | content (matching lines) | filename." }
107132
+ },
107133
+ required: ["pattern"]
107134
+ }
107135
+ },
107136
+ {
107137
+ name: "list_directory",
107138
+ description: "List the files and directories at a path.",
107139
+ input_schema: { type: "object", properties: { path: { type: "string" } } }
107140
+ },
107141
+ {
107142
+ name: "glob",
107143
+ description: 'Find files matching a glob pattern, e.g. "**/*.ts".',
107144
+ input_schema: { type: "object", properties: { pattern: { type: "string" }, path: { type: "string" } }, required: ["pattern"] }
107145
+ }
107146
+ ];
107147
+ var NO_TOOLS_MARKER = "\0hook-no-tools";
107148
+ function parsePromptHookReply(reply) {
107149
+ const text = String(reply ?? "");
107150
+ const start = text.indexOf("{");
107151
+ const end = text.lastIndexOf("}");
107152
+ if (start < 0 || end <= start)
107153
+ return null;
107154
+ try {
107155
+ const j = JSON.parse(text.slice(start, end + 1));
107156
+ if (typeof j.ok !== "boolean" && typeof j.decision !== "string")
107157
+ return null;
107158
+ return {
107159
+ ok: typeof j.ok === "boolean" ? j.ok : void 0,
107160
+ reason: typeof j.reason === "string" ? j.reason : void 0,
107161
+ additionalContext: typeof j.additionalContext === "string" ? j.additionalContext : void 0,
107162
+ decision: typeof j.decision === "string" ? j.decision : void 0
107163
+ };
107164
+ } catch {
107165
+ return null;
107166
+ }
107167
+ }
107168
+ var PROMPT_HOOK_RULES = `You are a hook evaluator for a coding agent. The hook input JSON is given below, followed by the hook's own instruction. Decide whether the agent action described in the input should proceed. Reply with ONLY one JSON object and nothing else: {"ok":true} to allow, or {"ok":false,"reason":"short explanation"} to block.`;
107169
+ async function promptHook(hook, payload, opts) {
107170
+ const instruction = String(hook.prompt ?? "");
107171
+ if (!instruction)
107172
+ return { status: 1, stdout: "", stderr: 'prompt hook has no "prompt"' };
107173
+ const body = instruction.replace(/\\\$ARGUMENTS/g, "").replace(/\$ARGUMENTS/g, payload).replace(/\u0001/g, "$ARGUMENTS");
107174
+ const ctl = { aborted: false };
107175
+ const timer = setTimeout(() => {
107176
+ ctl.aborted = true;
107177
+ }, Math.max(1e3, opts.timeoutMs));
107178
+ const outerPoll = opts.abortSignal ? setInterval(() => {
107179
+ if (opts.abortSignal?.aborted)
107180
+ ctl.aborted = true;
107181
+ }, 100) : void 0;
107182
+ if (opts.abortSignal?.aborted) {
107183
+ clearTimeout(timer);
107184
+ if (outerPoll)
107185
+ clearInterval(outerPoll);
107186
+ return { status: 1, stdout: "", stderr: "aborted" };
107187
+ }
107188
+ try {
107189
+ return await runModel();
107190
+ } finally {
107191
+ clearTimeout(timer);
107192
+ if (outerPoll)
107193
+ clearInterval(outerPoll);
107194
+ }
107195
+ async function runModel() {
107196
+ const messages = [{
107197
+ role: "user",
107198
+ content: [{ type: "text", text: `${PROMPT_HOOK_RULES}
107199
+
107200
+ Hook input:
107201
+ ${payload}
107202
+
107203
+ Hook instruction:
107204
+ ${body}` }]
107205
+ }];
107206
+ const isAgent = hook.type === "agent";
107207
+ try {
107208
+ let replyText = "";
107209
+ for (let round = 0; round < (isAgent ? 6 : 1); round++) {
107210
+ const msg = await (0, client_1.streamChat)(messages, {
107211
+ model: hook.model ?? opts.model ?? "turbo",
107212
+ env: opts.envCtx ?? { cwd: opts.cwd },
107213
+ abortSignal: ctl,
107214
+ // Wire the tools by allowlist rather than a separate channel: the backend
107215
+ // filters builtins+extra against it, so an agent hook sees ONLY the four
107216
+ // read-only tools and a prompt hook sees none (the marker matches nothing).
107217
+ toolAllowlist: isAgent ? HOOK_AGENT_TOOLS.map((t2) => t2.name) : [NO_TOOLS_MARKER],
107218
+ extraTools: isAgent ? HOOK_AGENT_TOOLS : void 0
107219
+ }, () => {
107220
+ });
107221
+ const blocks = Array.isArray(msg.content) ? msg.content : [];
107222
+ const toolUses = blocks.filter((b) => b.type === "tool_use");
107223
+ if (!toolUses.length || !isAgent) {
107224
+ replyText = blocks.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
107225
+ break;
107226
+ }
107227
+ messages.push({ role: "assistant", content: msg.content });
107228
+ const results = [];
107229
+ for (const tu of toolUses) {
107230
+ let out;
107231
+ try {
107232
+ out = getHookToolExecutors().includes(String(tu.name)) ? await (0, executor_1.executeTool)(String(tu.name), tu.input ?? {}, ctl, void 0, opts.cwd) : { error: `Tool ${String(tu.name)} is not available to a hook \u2014 only ${getHookToolExecutors().join(", ")}.` };
107233
+ } catch (err) {
107234
+ out = { error: err.message || "tool failed" };
107235
+ }
107236
+ results.push({
107237
+ type: "tool_result",
107238
+ tool_use_id: String(tu.id),
107239
+ content: out.error ? out.output ? `Error: ${out.error}
107240
+
107241
+ ${out.output}` : `Error: ${out.error}` : out.output ?? "",
107242
+ is_error: out.error !== void 0
107243
+ });
107244
+ }
107245
+ messages.push({ role: "user", content: results });
107246
+ }
107247
+ const parsed = parsePromptHookReply(replyText);
107248
+ if (!parsed) {
107249
+ return { status: 1, stdout: "", stderr: "prompt hook returned no decision" };
107250
+ }
107251
+ const decided = parsed.ok === false || parsed.decision === "block" || parsed.decision === "deny";
107252
+ if (decided)
107253
+ return { status: 2, stdout: "", stderr: parsed.reason || `Blocked by ${hook.type} hook` };
107254
+ const passthrough2 = parsed.additionalContext ? JSON.stringify({ additionalContext: parsed.additionalContext }) : "";
107255
+ return { status: 0, stdout: passthrough2, stderr: "" };
107256
+ } catch (err) {
107257
+ if (ctl.aborted)
107258
+ return { status: 1, stdout: "", stderr: "prompt hook timed out" };
107259
+ return { status: 1, stdout: "", stderr: `prompt hook failed: ${err.message}` };
107260
+ }
107261
+ }
107262
+ }
107263
+ function getHookToolExecutors() {
107264
+ return ["read_file", "search_files", "list_directory", "glob"];
107265
+ }
107266
+ function hookRunOptsFor(options) {
107267
+ return { model: options.model ?? "turbo", abortSignal: options.abortSignal, hookEnv: options.env };
107268
+ }
107269
+ function classifyStopFailure(err) {
107270
+ const status = err?.status;
107271
+ const code = err?.code;
107272
+ const details = [status ? `HTTP ${status}` : "", code ? String(code) : ""].filter(Boolean).join(" ") || void 0;
107273
+ if (status === 429)
107274
+ return { error: "rate_limit", error_details: details };
107275
+ if (status === 529)
107276
+ return { error: "overloaded", error_details: details };
107277
+ if (status === 401)
107278
+ return { error: "authentication_failed", error_details: details };
107279
+ if (status === 402)
107280
+ return { error: "billing_error", error_details: details };
107281
+ if (status === 403)
107282
+ return { error: "authentication_failed", error_details: details };
107283
+ if (status === 400 || status === 422)
107284
+ return { error: "invalid_request", error_details: details };
107285
+ if (status === 404)
107286
+ return { error: "model_not_found", error_details: details };
107287
+ if (typeof status === "number" && status >= 500)
107288
+ return { error: "server_error", error_details: details };
107289
+ return { error: "unknown", error_details: details };
107290
+ }
107291
+ function apiFailureForStopReason(stopReason) {
107292
+ if (stopReason === "output-limit")
107293
+ return { error: "max_output_tokens" };
107294
+ if (stopReason === "no-balance")
107295
+ return { error: "billing_error", error_details: "wallet empty (402)" };
107296
+ if (stopReason === "no-team-budget")
107297
+ return { error: "billing_error", error_details: "team pool empty (402)" };
107298
+ if (stopReason === "reported-elsewhere")
107299
+ return { error: "billing_error", error_details: "pre-flight 402" };
107300
+ if (stopReason === "team-unavailable")
107301
+ return { error: "account_on_hold", error_details: "team billing unusable (403)" };
107302
+ return null;
107303
+ }
107103
107304
  function hookTimeoutMs(hook, fallbackMs = 6e4) {
107104
107305
  if (typeof hook.timeout_ms === "number" && hook.timeout_ms > 0)
107105
107306
  return Math.min(hook.timeout_ms, 6e5);
@@ -107115,7 +107316,7 @@ var require_loop = __commonJS({
107115
107316
  return norm === toolName || norm === alt && toolName.includes(alt);
107116
107317
  });
107117
107318
  }
107118
- async function runToolHooks(entries, phase, toolName, input, workDir, result) {
107319
+ async function runToolHooks(entries, phase, toolName, input, workDir, result, hookOpts) {
107119
107320
  const outcome = { block: false };
107120
107321
  if (!entries?.length)
107121
107322
  return outcome;
@@ -107129,11 +107330,7 @@ var require_loop = __commonJS({
107129
107330
  if (!hookMatches(entry.matcher, toolName))
107130
107331
  continue;
107131
107332
  for (const hook of entry.hooks ?? []) {
107132
- if (hook.type !== "command" && hook.type !== "http")
107133
- continue;
107134
- if (hook.type === "command" && !hook.command)
107135
- continue;
107136
- if (hook.type === "http" && !hook.url)
107333
+ if (!isRunnableHook(hook))
107137
107334
  continue;
107138
107335
  const hookTimeout = hookTimeoutMs(hook, 6e4);
107139
107336
  const r2 = await runHookHandler(hook, payload, {
@@ -107144,7 +107341,8 @@ var require_loop = __commonJS({
107144
107341
  NEXRALL_TOOL_NAME: toolName,
107145
107342
  NEXRALL_TOOL_INPUT: JSON.stringify(input),
107146
107343
  NEXRALL_HOOK_PHASE: phase
107147
- }
107344
+ },
107345
+ ...hookOpts
107148
107346
  });
107149
107347
  const out = (r2.stdout ?? "").toString().trim();
107150
107348
  if (out.startsWith("{")) {
@@ -107169,17 +107367,13 @@ var require_loop = __commonJS({
107169
107367
  }
107170
107368
  return outcome;
107171
107369
  }
107172
- async function runSimpleHooks(defs, workDir, extraEnv) {
107370
+ async function runSimpleHooks(defs, workDir, extraEnv, hookOpts) {
107173
107371
  if (!defs?.length)
107174
107372
  return;
107175
107373
  for (const hook of defs) {
107176
- if (hook.type !== "command" && hook.type !== "http")
107177
- continue;
107178
- if (hook.type === "command" && !hook.command)
107374
+ if (!isRunnableHook(hook))
107179
107375
  continue;
107180
- if (hook.type === "http" && !hook.url)
107181
- continue;
107182
- await runHookHandler(hook, "", { cwd: workDir, timeoutMs: hookTimeoutMs(hook, 1e4), env: extraEnv ? { ...process.env, ...extraEnv } : void 0 });
107376
+ await runHookHandler(hook, "", { cwd: workDir, timeoutMs: hookTimeoutMs(hook, 1e4), env: extraEnv ? { ...process.env, ...extraEnv } : void 0, ...hookOpts });
107183
107377
  }
107184
107378
  }
107185
107379
  function flattenHookDefs(items, matchValue) {
@@ -107190,26 +107384,34 @@ var require_loop = __commonJS({
107190
107384
  if (m2 && m2 !== "*" && matchValue !== void 0 && !m2.split("|").map((x2) => x2.trim()).includes(matchValue))
107191
107385
  continue;
107192
107386
  out.push(...it.hooks);
107193
- } else if (it && (it.type === "command" || it.type === "http"))
107387
+ } else if (it && isKnownHandlerType(it.type))
107194
107388
  out.push(it);
107195
107389
  }
107196
107390
  return out;
107197
107391
  }
107198
- async function runLifecycleHooks(items, event, workDir, payload, matchValue) {
107392
+ function isKnownHandlerType(t2) {
107393
+ return t2 === "command" || t2 === "http" || t2 === "prompt" || t2 === "agent";
107394
+ }
107395
+ function isRunnableHook(hook) {
107396
+ if (!isKnownHandlerType(hook.type))
107397
+ return false;
107398
+ if (hook.type === "command")
107399
+ return !!hook.command;
107400
+ if (hook.type === "http")
107401
+ return !!hook.url;
107402
+ return !!hook.prompt;
107403
+ }
107404
+ async function runLifecycleHooks(items, event, workDir, payload, matchValue, hookOpts) {
107199
107405
  const outcome = { block: false };
107200
107406
  const defs = flattenHookDefs(items, matchValue);
107201
107407
  if (!defs.length)
107202
107408
  return outcome;
107203
107409
  const input = JSON.stringify({ hook_event_name: event, cwd: workDir, ...payload });
107204
107410
  for (const hook of defs) {
107205
- if (hook.type !== "command" && hook.type !== "http")
107206
- continue;
107207
- if (hook.type === "command" && !hook.command)
107411
+ if (!isRunnableHook(hook))
107208
107412
  continue;
107209
- if (hook.type === "http" && !hook.url)
107210
- continue;
107211
- const timeout = hookTimeoutMs(hook, 3e4);
107212
- const r2 = await runHookHandler(hook, input, { cwd: workDir, timeoutMs: timeout, env: { ...process.env, NEXRALL_HOOK_PHASE: event } });
107413
+ const timeout = hookTimeoutMs(hook, hook.type === "agent" ? 6e4 : hook.type === "prompt" ? 3e4 : 3e4);
107414
+ const r2 = await runHookHandler(hook, input, { cwd: workDir, timeoutMs: timeout, env: { ...process.env, NEXRALL_HOOK_PHASE: event }, ...hookOpts });
107213
107415
  const out = (r2.stdout ?? "").toString().trim();
107214
107416
  if (out.startsWith("{")) {
107215
107417
  try {
@@ -107229,6 +107431,10 @@ var require_loop = __commonJS({
107229
107431
  }
107230
107432
  if (typeof j.additionalContext === "string" && j.additionalContext)
107231
107433
  outcome.context = (outcome.context ? outcome.context + "\n" : "") + j.additionalContext;
107434
+ if (event === "FileChanged" && Array.isArray(j.watchPaths))
107435
+ outcome.watchPaths = j.watchPaths.filter((p) => typeof p === "string");
107436
+ if (typeof j.systemMessage === "string" && j.systemMessage)
107437
+ outcome.systemMessage = j.systemMessage;
107232
107438
  } catch {
107233
107439
  }
107234
107440
  } else if (out && (event === "SessionStart" || event === "UserPromptSubmit") && r2.status === 0) {
@@ -108089,7 +108295,7 @@ ${stats}${isoNote}`;
108089
108295
  const subAgentName = agent?.name ?? (typeof input.subagent_type === "string" && input.subagent_type ? input.subagent_type : "general-purpose");
108090
108296
  const startItems = loadHooks(options.workDir).SubagentStart;
108091
108297
  if (startItems?.length) {
108092
- const o = await runLifecycleHooks(startItems, "SubagentStart", options.workDir, { session_id: options.sessionId ?? "", agent_type: subAgentName, description: String(input.description ?? "") }, subAgentName).catch(() => ({ block: false }));
108298
+ const o = await runLifecycleHooks(startItems, "SubagentStart", options.workDir, { session_id: options.sessionId ?? "", agent_type: subAgentName, description: String(input.description ?? "") }, subAgentName, hookRunOptsFor(options)).catch(() => ({ block: false }));
108093
108299
  if (o.context) {
108094
108300
  const last = subMessages[subMessages.length - 1];
108095
108301
  if (last && Array.isArray(last.content)) {
@@ -108649,7 +108855,7 @@ Do NOT call any tools and do NOT continue the task. Reply with text only: a conc
108649
108855
  if (cut < 2)
108650
108856
  return false;
108651
108857
  if ((options._depth ?? 0) === 0) {
108652
- await runLifecycleHooks(loadHooks(options.workDir).PreCompact, "PreCompact", options.workDir, { trigger: "auto", session_id: options.sessionId ?? "", messages_to_summarize: cut }, "auto").catch(() => void 0);
108858
+ await runLifecycleHooks(loadHooks(options.workDir).PreCompact, "PreCompact", options.workDir, { trigger: "auto", session_id: options.sessionId ?? "", messages_to_summarize: cut }, "auto", hookRunOptsFor(options)).catch(() => void 0);
108653
108859
  }
108654
108860
  const beforeCompact = messages.length;
108655
108861
  const afterCompact = cut > 0 ? messages.length - cut + 1 : messages.length;
@@ -108662,7 +108868,7 @@ Do NOT call any tools and do NOT continue the task. Reply with text only: a conc
108662
108868
  options.onCompact?.({ trigger: "auto", before: beforeCompact, after: afterCompact });
108663
108869
  } catch {
108664
108870
  }
108665
- await runLifecycleHooks(loadHooks(options.workDir).PostCompact, "PostCompact", options.workDir, { trigger: "auto", session_id: options.sessionId ?? "", messages_summarized: cut }, "auto").catch(() => void 0);
108871
+ await runLifecycleHooks(loadHooks(options.workDir).PostCompact, "PostCompact", options.workDir, { trigger: "auto", session_id: options.sessionId ?? "", messages_summarized: cut }, "auto", hookRunOptsFor(options)).catch(() => void 0);
108666
108872
  };
108667
108873
  const originalTask = originalTaskText(toSummarize);
108668
108874
  const summaryPrompt = `Summarize this coding-session transcript into concise bullet points the assistant needs to continue the work: key decisions, files changed (and how), commands run, unresolved problems, and user preferences. Max 400 words.
@@ -108910,6 +109116,26 @@ Continue the work from here.` }] });
108910
109116
  const messages = [...initialMessages];
108911
109117
  const model = options.model ?? "turbo";
108912
109118
  const hooks = options.disableHooks ? withAgentHooks({}, options._agentHooks) : withAgentHooks(loadHooks(options.workDir), options._agentHooks);
109119
+ const fireStopFailure = async (failure) => {
109120
+ if (depth !== 0)
109121
+ return;
109122
+ const items = hooks.StopFailure;
109123
+ if (!items?.length)
109124
+ return;
109125
+ let last = "";
109126
+ for (let i2 = messages.length - 1; i2 >= 0 && !last; i2--) {
109127
+ const m2 = messages[i2];
109128
+ if (m2?.role === "assistant" && Array.isArray(m2.content)) {
109129
+ last = m2.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n").trim().slice(0, 2e3);
109130
+ }
109131
+ }
109132
+ await runLifecycleHooks(items, "StopFailure", options.workDir, {
109133
+ session_id: options.sessionId ?? "",
109134
+ error: failure.error,
109135
+ ...failure.error_details ? { error_details: failure.error_details } : {},
109136
+ last_assistant_message: last
109137
+ }, failure.error, hookRunOptsFor(options)).catch(() => void 0);
109138
+ };
108913
109139
  const depth = options._depth ?? 0;
108914
109140
  const agentScope = options._agentScope ?? "root";
108915
109141
  const isMainAgent = depth === 0;
@@ -108930,13 +109156,13 @@ ${ctx}
108930
109156
  const sid = options.sessionId || PROCESS_BUDGET_KEY;
108931
109157
  if (isPrompt && !_sessionStarted.has(sid) && (hooks.SessionStart?.length ?? 0) > 0) {
108932
109158
  _sessionStarted.add(sid);
108933
- const o = await runLifecycleHooks(hooks.SessionStart, "SessionStart", options.workDir, { session_id: options.sessionId ?? "", source: messages.length > 1 ? "resume" : "startup" }, messages.length > 1 ? "resume" : "startup");
109159
+ const o = await runLifecycleHooks(hooks.SessionStart, "SessionStart", options.workDir, { session_id: options.sessionId ?? "", source: messages.length > 1 ? "resume" : "startup" }, messages.length > 1 ? "resume" : "startup", hookRunOptsFor(options));
108934
109160
  if (o.context)
108935
109161
  addContext(o.context);
108936
109162
  } else if (isPrompt)
108937
109163
  _sessionStarted.add(sid);
108938
109164
  if (isPrompt && (hooks.UserPromptSubmit?.length ?? 0) > 0) {
108939
- const o = await runLifecycleHooks(hooks.UserPromptSubmit, "UserPromptSubmit", options.workDir, { session_id: options.sessionId ?? "", prompt: promptText });
109165
+ const o = await runLifecycleHooks(hooks.UserPromptSubmit, "UserPromptSubmit", options.workDir, { session_id: options.sessionId ?? "", prompt: promptText }, void 0, hookRunOptsFor(options));
108940
109166
  if (o.block) {
108941
109167
  options.onText(`
108942
109168
  \u26D4 Prompt blocked by a UserPromptSubmit hook: ${o.reason ?? "no reason given"}
@@ -109204,7 +109430,8 @@ ${ctx}
109204
109430
  stopReason = "team-unavailable";
109205
109431
  break;
109206
109432
  }
109207
- await runSimpleHooks(hooks.OnError, options.workDir);
109433
+ await runSimpleHooks(hooks.OnError, options.workDir, void 0, hookRunOptsFor(options));
109434
+ await fireStopFailure(classifyStopFailure(err));
109208
109435
  throw new types_1.AgentTurnError(`Stream failed: ${err.message}`, trimToResumableBoundary(messages), completedRounds, err);
109209
109436
  }
109210
109437
  if (options.abortSignal?.aborted) {
@@ -109245,7 +109472,7 @@ ${ctx}
109245
109472
  continue;
109246
109473
  }
109247
109474
  if (depth === 0)
109248
- await runSimpleHooks(hooks.PostMessageComplete, options.workDir);
109475
+ await runSimpleHooks(hooks.PostMessageComplete, options.workDir, void 0, hookRunOptsFor(options));
109249
109476
  stopReason = assistantMessage.stopReason === "max_tokens" ? "output-limit" : "empty-response";
109250
109477
  break;
109251
109478
  }
@@ -109329,7 +109556,7 @@ ${ctx}
109329
109556
  }
109330
109557
  }
109331
109558
  if (depth === 0)
109332
- await runSimpleHooks(hooks.PostMessageComplete, options.workDir);
109559
+ await runSimpleHooks(hooks.PostMessageComplete, options.workDir, void 0, hookRunOptsFor(options));
109333
109560
  stopReason = "clean";
109334
109561
  break;
109335
109562
  }
@@ -109377,12 +109604,12 @@ ${ctx}
109377
109604
  } else if (name === "task") {
109378
109605
  const wantsBackground = input.run_in_background === true || input.run_in_background !== false && !!(0, agentTypes_1.findAgentType)(agentTypes, String(input.subagent_type ?? ""))?.background;
109379
109606
  const taskOptions = { ...auditOptions, _taskToolUseId: id };
109380
- const preTask = await runToolHooks(hooks.PreToolUse, "PreToolUse", name, input, options.workDir);
109607
+ const preTask = await runToolHooks(hooks.PreToolUse, "PreToolUse", name, input, options.workDir, void 0, hookRunOptsFor(options));
109381
109608
  if (preTask.block) {
109382
109609
  result = { error: `Blocked by PreToolUse hook: ${preTask.reason}` };
109383
109610
  } else {
109384
109611
  result = wantsBackground && depth === 0 && options.backgroundAgents && !input.resume_agent_id ? dispatchBackgroundSubAgent(input, taskOptions, agentTypes, options.backgroundAgents) : await dispatchSubAgent(input, taskOptions, agentTypes);
109385
- const postTask = await runToolHooks(hooks.PostToolUse, "PostToolUse", name, input, options.workDir, result);
109612
+ const postTask = await runToolHooks(hooks.PostToolUse, "PostToolUse", name, input, options.workDir, result, hookRunOptsFor(options));
109386
109613
  const injectedTask = [preTask.context, postTask.context].filter(Boolean).join("\n");
109387
109614
  if (injectedTask) {
109388
109615
  if (result.error !== void 0)
@@ -109399,7 +109626,7 @@ ${ctx}
109399
109626
  }
109400
109627
  }
109401
109628
  } else {
109402
- const pre = await runToolHooks(hooks.PreToolUse, "PreToolUse", name, input, options.workDir);
109629
+ const pre = await runToolHooks(hooks.PreToolUse, "PreToolUse", name, input, options.workDir, void 0, hookRunOptsFor(options));
109403
109630
  if (pre.block) {
109404
109631
  result = { error: `Blocked by PreToolUse hook: ${pre.reason}` };
109405
109632
  } else {
@@ -109445,9 +109672,9 @@ ${ctx}
109445
109672
  const scope = input.scope === "global" ? "global" : "project";
109446
109673
  void maybeCompactMemory(scope, options);
109447
109674
  }
109448
- const post = await runToolHooks(hooks.PostToolUse, "PostToolUse", name, input, options.workDir, result);
109675
+ const post = await runToolHooks(hooks.PostToolUse, "PostToolUse", name, input, options.workDir, result, hookRunOptsFor(options));
109449
109676
  if (result.error !== void 0 && (hooks.PostToolUseFailure?.length ?? 0) > 0) {
109450
- const f3 = await runLifecycleHooks(hooks.PostToolUseFailure, "PostToolUseFailure", options.workDir, { session_id: options.sessionId ?? "", tool_name: name, tool_input: input, error: String(result.error).slice(0, 4e3) }, name).catch(() => ({ block: false }));
109677
+ const f3 = await runLifecycleHooks(hooks.PostToolUseFailure, "PostToolUseFailure", options.workDir, { session_id: options.sessionId ?? "", tool_name: name, tool_input: input, error: String(result.error).slice(0, 4e3) }, name, hookRunOptsFor(options)).catch(() => ({ block: false }));
109451
109678
  if (f3.context)
109452
109679
  post.context = [post.context, f3.context].filter(Boolean).join("\n");
109453
109680
  if (f3.block) {
@@ -109569,6 +109796,9 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
109569
109796
  stopReason = "budget";
109570
109797
  if (options.abortSignal?.aborted)
109571
109798
  stopReason = "aborted";
109799
+ const apiFailure = apiFailureForStopReason(stopReason);
109800
+ if (apiFailure)
109801
+ await fireStopFailure(apiFailure);
109572
109802
  const notice = stopReasonNotice(stopReason, { budget, repeatError: stalledRepeatError });
109573
109803
  if (options._onStopReason)
109574
109804
  options._onStopReason(stopReason, notice || null);
@@ -109583,12 +109813,12 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
109583
109813
  throw new types_1.AgentTurnError(err?.message || "The turn ended unexpectedly", trimToResumableBoundary(messages), completedRounds, err);
109584
109814
  } finally {
109585
109815
  if (depth === 0 && (hooks.SessionEnd?.length ?? 0) > 0) {
109586
- await runLifecycleHooks(hooks.SessionEnd, "SessionEnd", options.workDir, { session_id: options.sessionId ?? "", reason: stopReason }, stopReason).catch(() => void 0);
109816
+ await runLifecycleHooks(hooks.SessionEnd, "SessionEnd", options.workDir, { session_id: options.sessionId ?? "", reason: stopReason }, stopReason, hookRunOptsFor(options)).catch(() => void 0);
109587
109817
  }
109588
109818
  if (depth === 0)
109589
- await runSimpleHooks(hooks.OnStop, options.workDir);
109819
+ await runSimpleHooks(hooks.OnStop, options.workDir, void 0, hookRunOptsFor(options));
109590
109820
  else
109591
- await runSimpleHooks(hooks.SubagentStop, options.workDir, { NEXRALL_AGENT_NAME: options._agentTypeName ?? "general-purpose" });
109821
+ await runSimpleHooks(hooks.SubagentStop, options.workDir, { NEXRALL_AGENT_NAME: options._agentTypeName ?? "general-purpose" }, hookRunOptsFor(options));
109592
109822
  }
109593
109823
  return messages;
109594
109824
  }
@@ -109610,6 +109840,214 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
109610
109840
  }
109611
109841
  });
109612
109842
 
109843
+ // ../core/dist/agent/lifecycleHost.js
109844
+ var require_lifecycleHost = __commonJS({
109845
+ "../core/dist/agent/lifecycleHost.js"(exports2) {
109846
+ "use strict";
109847
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? function(o, m2, k, k2) {
109848
+ if (k2 === void 0)
109849
+ k2 = k;
109850
+ var desc = Object.getOwnPropertyDescriptor(m2, k);
109851
+ if (!desc || ("get" in desc ? !m2.__esModule : desc.writable || desc.configurable)) {
109852
+ desc = { enumerable: true, get: function() {
109853
+ return m2[k];
109854
+ } };
109855
+ }
109856
+ Object.defineProperty(o, k2, desc);
109857
+ } : function(o, m2, k, k2) {
109858
+ if (k2 === void 0)
109859
+ k2 = k;
109860
+ o[k2] = m2[k];
109861
+ });
109862
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? function(o, v) {
109863
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
109864
+ } : function(o, v) {
109865
+ o["default"] = v;
109866
+ });
109867
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ function() {
109868
+ var ownKeys = function(o) {
109869
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
109870
+ var ar = [];
109871
+ for (var k in o2)
109872
+ if (Object.prototype.hasOwnProperty.call(o2, k))
109873
+ ar[ar.length] = k;
109874
+ return ar;
109875
+ };
109876
+ return ownKeys(o);
109877
+ };
109878
+ return function(mod) {
109879
+ if (mod && mod.__esModule)
109880
+ return mod;
109881
+ var result = {};
109882
+ if (mod != null) {
109883
+ for (var k = ownKeys(mod), i2 = 0; i2 < k.length; i2++)
109884
+ if (k[i2] !== "default")
109885
+ __createBinding(result, mod, k[i2]);
109886
+ }
109887
+ __setModuleDefault(result, mod);
109888
+ return result;
109889
+ };
109890
+ }();
109891
+ Object.defineProperty(exports2, "__esModule", { value: true });
109892
+ exports2.fireInstructionsLoadedHooks = fireInstructionsLoadedHooks2;
109893
+ exports2.startFileChangedWatcher = startFileChangedWatcher2;
109894
+ var fs28 = __importStar(__require("fs"));
109895
+ var path28 = __importStar(__require("path"));
109896
+ var loop_1 = require_loop();
109897
+ async function fireInstructionsLoadedHooks2(workDir, sessionId, files) {
109898
+ if (!files.length)
109899
+ return;
109900
+ try {
109901
+ const items = (0, loop_1.loadHooks)(workDir).InstructionsLoaded;
109902
+ if (!items?.length)
109903
+ return;
109904
+ for (const f3 of files) {
109905
+ await (0, loop_1.runLifecycleHooks)(items, "InstructionsLoaded", workDir, {
109906
+ session_id: sessionId,
109907
+ file_path: f3.file_path,
109908
+ memory_type: f3.memory_type,
109909
+ load_reason: f3.load_reason,
109910
+ ...f3.globs ? { globs: f3.globs } : {}
109911
+ }, f3.load_reason).catch(() => void 0);
109912
+ }
109913
+ } catch {
109914
+ }
109915
+ }
109916
+ function isWatchableName(name) {
109917
+ return !!name && !name.includes("/") && !name.includes("\\") && !name.includes("*") && !name.includes("?") && name !== "." && name !== "..";
109918
+ }
109919
+ function startFileChangedWatcher2(workDir, sessionId, opts) {
109920
+ let items;
109921
+ try {
109922
+ items = (0, loop_1.loadHooks)(workDir).FileChanged;
109923
+ } catch {
109924
+ items = void 0;
109925
+ }
109926
+ if (!items?.length)
109927
+ return null;
109928
+ const staticNames = /* @__PURE__ */ new Set();
109929
+ for (const it of items) {
109930
+ if (it && Array.isArray(it.hooks)) {
109931
+ const m2 = it.matcher;
109932
+ if (!m2 || m2 === "*")
109933
+ continue;
109934
+ for (const raw of m2.split("|")) {
109935
+ const name = raw.trim();
109936
+ if (isWatchableName(name))
109937
+ staticNames.add(name);
109938
+ }
109939
+ }
109940
+ }
109941
+ if (!staticNames.size)
109942
+ return null;
109943
+ const dynamicNames = /* @__PURE__ */ new Set();
109944
+ const watched = () => /* @__PURE__ */ new Set([...staticNames, ...dynamicNames]);
109945
+ const known = /* @__PURE__ */ new Set();
109946
+ const seedKnown = (names) => {
109947
+ for (const n of names) {
109948
+ try {
109949
+ if (fs28.existsSync(path28.join(workDir, n)))
109950
+ known.add(n);
109951
+ } catch {
109952
+ }
109953
+ }
109954
+ };
109955
+ seedKnown(staticNames);
109956
+ const pending = /* @__PURE__ */ new Map();
109957
+ let closed = false;
109958
+ let watcher = null;
109959
+ try {
109960
+ watcher = fs28.watch(workDir, { persistent: false }, (_eventType, filename) => {
109961
+ if (closed)
109962
+ return;
109963
+ const name = filename ? path28.basename(String(filename)) : "";
109964
+ if (!name || !watched().has(name))
109965
+ return;
109966
+ const prev = pending.get(name);
109967
+ if (prev)
109968
+ clearTimeout(prev);
109969
+ pending.set(name, setTimeout(() => {
109970
+ pending.delete(name);
109971
+ let exists = true;
109972
+ try {
109973
+ exists = fs28.existsSync(path28.join(workDir, name));
109974
+ } catch {
109975
+ exists = false;
109976
+ }
109977
+ const event = !exists ? "unlink" : known.has(name) ? "change" : "add";
109978
+ if (exists)
109979
+ known.add(name);
109980
+ else
109981
+ known.delete(name);
109982
+ void fireChanged(workDir, sessionId, name, event, items, dynamicNames.has(name)).then((o) => {
109983
+ if (!o)
109984
+ return;
109985
+ if (o.systemMessage && opts?.onSystemMessage) {
109986
+ try {
109987
+ opts.onSystemMessage(o.systemMessage);
109988
+ } catch {
109989
+ }
109990
+ }
109991
+ if (o.watchPaths) {
109992
+ dynamicNames.clear();
109993
+ for (const p of o.watchPaths) {
109994
+ const n = isWatchableName(path28.basename(p)) ? path28.basename(p) : "";
109995
+ if (n)
109996
+ dynamicNames.add(n);
109997
+ }
109998
+ seedKnown(dynamicNames);
109999
+ }
110000
+ }).catch(() => {
110001
+ });
110002
+ }, 250));
110003
+ });
110004
+ watcher.on("error", () => {
110005
+ });
110006
+ } catch {
110007
+ return null;
110008
+ }
110009
+ return {
110010
+ stop() {
110011
+ closed = true;
110012
+ for (const t2 of pending.values())
110013
+ clearTimeout(t2);
110014
+ pending.clear();
110015
+ try {
110016
+ watcher?.close();
110017
+ } catch {
110018
+ }
110019
+ watcher = null;
110020
+ },
110021
+ updateWatchPaths(paths) {
110022
+ dynamicNames.clear();
110023
+ for (const p of paths) {
110024
+ const n = isWatchableName(path28.basename(p)) ? path28.basename(p) : "";
110025
+ if (n)
110026
+ dynamicNames.add(n);
110027
+ }
110028
+ seedKnown(dynamicNames);
110029
+ },
110030
+ watchedFiles() {
110031
+ return [...watched()].sort();
110032
+ }
110033
+ };
110034
+ }
110035
+ async function fireChanged(workDir, sessionId, name, event, items, isDynamic) {
110036
+ const abs = path28.join(workDir, name);
110037
+ try {
110038
+ const o = await (0, loop_1.runLifecycleHooks)(items, "FileChanged", workDir, {
110039
+ session_id: sessionId,
110040
+ file_path: abs,
110041
+ event
110042
+ }, isDynamic ? void 0 : name);
110043
+ return { watchPaths: o.watchPaths, systemMessage: o.systemMessage };
110044
+ } catch {
110045
+ return null;
110046
+ }
110047
+ }
110048
+ }
110049
+ });
110050
+
109613
110051
  // ../core/dist/mcp/client.js
109614
110052
  var require_client2 = __commonJS({
109615
110053
  "../core/dist/mcp/client.js"(exports2) {
@@ -111826,6 +112264,7 @@ var require_dist3 = __commonJS({
111826
112264
  __exportStar(require_client(), exports2);
111827
112265
  __exportStar(require_executor(), exports2);
111828
112266
  __exportStar(require_loop(), exports2);
112267
+ __exportStar(require_lifecycleHost(), exports2);
111829
112268
  __exportStar(require_modelCatalogue(), exports2);
111830
112269
  __exportStar(require_testIntegrity(), exports2);
111831
112270
  __exportStar(require_editCompleteness(), exports2);
@@ -169063,22 +169502,39 @@ var tryRead = (p) => {
169063
169502
  return null;
169064
169503
  }
169065
169504
  };
169505
+ var lastLoadedInstructions = [];
169066
169506
  function readNexrallMd(workDir) {
169507
+ lastLoadedInstructions = [];
169067
169508
  if (noDiscovery)
169068
169509
  return void 0;
169069
169510
  const parts = [];
169511
+ const noteLoaded = (file, memoryType) => {
169512
+ try {
169513
+ if (fs25.existsSync(file))
169514
+ lastLoadedInstructions.push({ file_path: file, memory_type: memoryType, load_reason: "session_start" });
169515
+ } catch {
169516
+ }
169517
+ };
169070
169518
  const memContent = (0, import_code_core16.readAllMemory)(workDir);
169071
- if (memContent)
169519
+ if (memContent) {
169072
169520
  parts.push(memContent);
169073
- const globalContent = tryRead(path25.join(os15.homedir(), ".nexrall", "nexrall.md"));
169074
- if (globalContent)
169521
+ noteLoaded((0, import_code_core16.memoryFilePath)("global"), "User");
169522
+ if (workDir)
169523
+ noteLoaded((0, import_code_core16.memoryFilePath)("project", workDir), "Project");
169524
+ }
169525
+ const globalNexrall = path25.join(os15.homedir(), ".nexrall", "nexrall.md");
169526
+ const globalContent = tryRead(globalNexrall);
169527
+ if (globalContent) {
169075
169528
  parts.push(`[Global instructions]
169076
169529
  ${globalContent}`);
169530
+ noteLoaded(globalNexrall, "User");
169531
+ }
169077
169532
  for (const candidate of NEXRALL_MD_CANDIDATES) {
169078
169533
  const content = tryRead(path25.join(workDir, candidate));
169079
169534
  if (content) {
169080
169535
  parts.push(`[Project instructions \u2014 ${candidate}]
169081
169536
  ${content}`);
169537
+ noteLoaded(path25.join(workDir, candidate), "Project");
169082
169538
  break;
169083
169539
  }
169084
169540
  }
@@ -170646,6 +171102,18 @@ Resume it with: nex --worktree ${worktreeState.name}
170646
171102
  let sessionId = options.newSessionId ?? crypto7.randomBytes(8).toString("hex");
170647
171103
  activeSessionIdGetter = () => sessionId;
170648
171104
  let sessionTitle = options.sessionName ?? "";
171105
+ const fileWatcher = noDiscovery ? null : (0, import_code_core16.startFileChangedWatcher)(workDir, sessionId, {
171106
+ // A hook's {"systemMessage":"…"} is the event's one user-visible field.
171107
+ onSystemMessage: (m2) => console.log(paint.dim(` ${m2}`))
171108
+ });
171109
+ if (fileWatcher) {
171110
+ const watching = fileWatcher.watchedFiles();
171111
+ if (watching.length && !headless)
171112
+ console.log(paint.dim(` FileChanged hook: watching ${watching.join(", ")}`));
171113
+ if (options.verbose)
171114
+ process.stderr.write("[verbose] FileChanged hook: " + watching.join(", ") + "\n");
171115
+ }
171116
+ void (0, import_code_core16.fireInstructionsLoadedHooks)(workDir, sessionId, lastLoadedInstructions);
170649
171117
  const modeLocked = options.lockMode === true;
170650
171118
  let agentMode = modeLocked ? "plan" : options.initialMode ?? "auto";
170651
171119
  const refuseIfLocked = (what) => {
@@ -173400,6 +173868,7 @@ ${text}
173400
173868
  deliverBackgroundWhenIdle();
173401
173869
  });
173402
173870
  rl.on("close", () => {
173871
+ fileWatcher?.stop();
173403
173872
  updateTitle();
173404
173873
  if (messages.length)
173405
173874
  saveSession2(sessionId, sessionTitle || "Untitled", workDir, messages);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexrall-code",
3
- "version": "0.5.166",
3
+ "version": "0.5.168",
4
4
  "description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
5
5
  "keywords": [
6
6
  "ai",