nexrall-code 0.5.177 → 0.5.179

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 +1049 -71
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14343,6 +14343,7 @@ var require_sharedTasks = __commonJS({
14343
14343
  exports2.createSharedTask = createSharedTask;
14344
14344
  exports2.claimTask = claimTask;
14345
14345
  exports2.updateSharedTaskStatus = updateSharedTaskStatus;
14346
+ exports2.deleteSharedTask = deleteSharedTask;
14346
14347
  var fs32 = __importStar(__require("fs"));
14347
14348
  var os20 = __importStar(__require("os"));
14348
14349
  var path32 = __importStar(__require("path"));
@@ -14476,6 +14477,14 @@ var require_sharedTasks = __commonJS({
14476
14477
  }, workDir);
14477
14478
  }
14478
14479
  exports2._sharedTasksInternal = { tasksRoot, scopeDir, taskPath, isClaimAbandoned, isUnblocked, CLAIM_STALE_MS, MIN_CLAIM_AGE_FOR_DEATH_CHECK_MS };
14480
+ function deleteSharedTask(workDir, taskId) {
14481
+ try {
14482
+ fs32.unlinkSync(taskPath(workDir, taskId));
14483
+ return true;
14484
+ } catch {
14485
+ return false;
14486
+ }
14487
+ }
14479
14488
  }
14480
14489
  });
14481
14490
 
@@ -106697,6 +106706,16 @@ var require_hooks = __commonJS({
106697
106706
  Object.defineProperty(exports2, "__esModule", { value: true });
106698
106707
  exports2.withAgentHooks = withAgentHooks;
106699
106708
  exports2.loadHooks = loadHooks2;
106709
+ exports2.setHookWakeListener = setHookWakeListener2;
106710
+ exports2.setHookStatusListener = setHookStatusListener2;
106711
+ exports2.drainHookDeliveries = drainHookDeliveries;
106712
+ exports2.takeHookWakes = takeHookWakes2;
106713
+ exports2.hasPendingHookWake = hasPendingHookWake2;
106714
+ exports2.formatHookDeliveries = formatHookDeliveries2;
106715
+ exports2.resetHookOnceState = resetHookOnceState2;
106716
+ exports2.setHookMcpCaller = setHookMcpCaller2;
106717
+ exports2.setHookEventListener = setHookEventListener2;
106718
+ exports2.hasHookEventListener = hasHookEventListener;
106700
106719
  exports2.parsePromptHookReply = parsePromptHookReply;
106701
106720
  exports2.hookRunOptsFor = hookRunOptsFor;
106702
106721
  exports2.classifyStopFailure = classifyStopFailure;
@@ -106705,6 +106724,8 @@ var require_hooks = __commonJS({
106705
106724
  exports2.runSimpleHooks = runSimpleHooks;
106706
106725
  exports2.runLifecycleHooks = runLifecycleHooks;
106707
106726
  exports2.runPermissionRequestHooks = runPermissionRequestHooks3;
106727
+ exports2.runElicitationHooks = runElicitationHooks2;
106728
+ exports2.runElicitationResultHooks = runElicitationResultHooks2;
106708
106729
  exports2.fireObserverHook = fireObserverHook3;
106709
106730
  exports2.fireNotificationHooks = fireNotificationHooks2;
106710
106731
  exports2.fireManualPostCompactHook = fireManualPostCompactHook2;
@@ -106786,7 +106807,12 @@ var require_hooks = __commonJS({
106786
106807
  "ConfigChange",
106787
106808
  "WorktreeCreate",
106788
106809
  "WorktreeRemove",
106789
- "Setup"
106810
+ "Setup",
106811
+ "PostToolBatch",
106812
+ "TaskCreated",
106813
+ "TaskCompleted",
106814
+ "Elicitation",
106815
+ "ElicitationResult"
106790
106816
  ];
106791
106817
  function toHookEntries(list) {
106792
106818
  return list.map((it) => {
@@ -106795,11 +106821,206 @@ var require_hooks = __commonJS({
106795
106821
  return { matcher: e2.matcher, hooks: e2.hooks };
106796
106822
  const { matcher, ...rest } = e2;
106797
106823
  const hook = { ...rest };
106798
- if (!isKnownHandlerType(hook.type))
106799
- hook.type = "command";
106824
+ if (!isKnownHandlerType(hook.type)) {
106825
+ hook.type = !hook.command && !hook.url && !hook.prompt && hook.server && hook.tool ? "mcp_tool" : "command";
106826
+ }
106800
106827
  return { matcher, hooks: [hook] };
106801
106828
  });
106802
106829
  }
106830
+ var _hookDeliveries = [];
106831
+ var _hookWakeListener = null;
106832
+ var _hookStatusListener = null;
106833
+ function setHookWakeListener2(fn) {
106834
+ _hookWakeListener = fn;
106835
+ }
106836
+ function setHookStatusListener2(fn) {
106837
+ _hookStatusListener = fn;
106838
+ }
106839
+ function drainHookDeliveries() {
106840
+ return _hookDeliveries.splice(0);
106841
+ }
106842
+ function takeHookWakes2() {
106843
+ const out = _hookDeliveries.filter((d) => d.kind === "wake");
106844
+ for (let i2 = _hookDeliveries.length - 1; i2 >= 0; i2--)
106845
+ if (_hookDeliveries[i2].kind === "wake")
106846
+ _hookDeliveries.splice(i2, 1);
106847
+ return out;
106848
+ }
106849
+ function hasPendingHookWake2() {
106850
+ return _hookDeliveries.some((d) => d.kind === "wake");
106851
+ }
106852
+ function formatHookDeliveries2(list) {
106853
+ return list.map((d) => `<hook-context source="${d.source}">
106854
+ ${d.text}
106855
+ </hook-context>`).join("\n\n");
106856
+ }
106857
+ var _hookOnceDone = /* @__PURE__ */ new Set();
106858
+ function hookIdentity(event, hook, matcher, dir) {
106859
+ const body = hook.command ?? hook.url ?? hook.prompt ?? `${hook.server ?? ""}__${hook.tool ?? ""}`;
106860
+ return `${dir ?? ""}|${event}|${matcher ?? ""}|${body}`;
106861
+ }
106862
+ function resetHookOnceState2() {
106863
+ _hookOnceDone.clear();
106864
+ }
106865
+ var _activeStatuses = /* @__PURE__ */ new Map();
106866
+ var _statusSeq = 0;
106867
+ function hookStatusBegin(message) {
106868
+ const id = ++_statusSeq;
106869
+ _activeStatuses.set(id, message);
106870
+ _hookStatusListener?.(message);
106871
+ return id;
106872
+ }
106873
+ function hookStatusEnd(id) {
106874
+ if (!_activeStatuses.delete(id))
106875
+ return;
106876
+ if (!_activeStatuses.size) {
106877
+ _hookStatusListener?.(null);
106878
+ return;
106879
+ }
106880
+ _hookStatusListener?.([..._activeStatuses.values()].pop() ?? null);
106881
+ }
106882
+ var _hookMcpCaller = null;
106883
+ function setHookMcpCaller2(fn) {
106884
+ _hookMcpCaller = fn;
106885
+ }
106886
+ var _hookEventListener = null;
106887
+ function setHookEventListener2(fn) {
106888
+ _hookEventListener = fn;
106889
+ }
106890
+ function hasHookEventListener() {
106891
+ return _hookEventListener !== null;
106892
+ }
106893
+ var _hookIdSeq = 0;
106894
+ function emitHookLifecycle(evt) {
106895
+ try {
106896
+ _hookEventListener?.(evt);
106897
+ } catch {
106898
+ }
106899
+ }
106900
+ function hookDisplayName(hook) {
106901
+ if (hook.type === "http")
106902
+ return String(hook.url ?? "http");
106903
+ if (hook.type === "mcp_tool")
106904
+ return `${String(hook.server ?? "")}:${String(hook.tool ?? "")}`;
106905
+ if (hook.type === "prompt" || hook.type === "agent")
106906
+ return hook.type;
106907
+ return String(hook.command ?? "command");
106908
+ }
106909
+ var HOOK_PROGRESS_AFTER_MS = 1e3;
106910
+ var HOOK_PROGRESS_FLUSH_MS = 250;
106911
+ async function runWithHookEvents(hook, event, run) {
106912
+ if (!_hookEventListener)
106913
+ return run(void 0);
106914
+ const hookId = `hook_${Date.now().toString(36)}_${(++_hookIdSeq).toString(36)}`;
106915
+ const hookName = hookDisplayName(hook);
106916
+ const startedAt = Date.now();
106917
+ emitHookLifecycle({ kind: "started", hookId, hookName, hookEvent: event });
106918
+ let pendingOut = "";
106919
+ let pendingErr = "";
106920
+ let lastFlush = 0;
106921
+ let flushTimer = null;
106922
+ const flush = () => {
106923
+ flushTimer = null;
106924
+ if (!pendingOut && !pendingErr)
106925
+ return;
106926
+ const stdout2 = pendingOut, stderr2 = pendingErr;
106927
+ pendingOut = "";
106928
+ pendingErr = "";
106929
+ lastFlush = Date.now();
106930
+ emitHookLifecycle({ kind: "progress", hookId, hookName, hookEvent: event, stdout: stdout2, stderr: stderr2, output: stdout2 + stderr2 });
106931
+ };
106932
+ const onData = (d) => {
106933
+ if (Date.now() - startedAt < HOOK_PROGRESS_AFTER_MS)
106934
+ return;
106935
+ if (d.stdout)
106936
+ pendingOut += d.stdout;
106937
+ if (d.stderr)
106938
+ pendingErr += d.stderr;
106939
+ if (!pendingOut && !pendingErr)
106940
+ return;
106941
+ const since = Date.now() - lastFlush;
106942
+ if (since >= HOOK_PROGRESS_FLUSH_MS)
106943
+ flush();
106944
+ else if (!flushTimer)
106945
+ flushTimer = setTimeout(flush, HOOK_PROGRESS_FLUSH_MS - since);
106946
+ };
106947
+ let r2;
106948
+ try {
106949
+ r2 = await run(onData);
106950
+ } catch (err) {
106951
+ if (flushTimer)
106952
+ clearTimeout(flushTimer);
106953
+ const message = err?.message ?? String(err);
106954
+ emitHookLifecycle({ kind: "response", hookId, hookName, hookEvent: event, stdout: "", stderr: message, output: message, outcome: "error" });
106955
+ throw err;
106956
+ }
106957
+ if (flushTimer)
106958
+ clearTimeout(flushTimer);
106959
+ const stdout = r2.stdout ?? "";
106960
+ const stderr = r2.stderr ?? "";
106961
+ emitHookLifecycle({
106962
+ kind: "response",
106963
+ hookId,
106964
+ hookName,
106965
+ hookEvent: event,
106966
+ stdout,
106967
+ stderr,
106968
+ output: [stdout.trim(), stderr.trim()].filter(Boolean).join("\n"),
106969
+ // status null = killed / never exited (spawnHook's timeout path) — there is no exit code to report.
106970
+ ...r2.status === null ? {} : { exitCode: r2.status },
106971
+ outcome: r2.status === 0 ? "success" : r2.status === null ? "cancelled" : "error"
106972
+ });
106973
+ return r2;
106974
+ }
106975
+ function isBackgroundHook(hook) {
106976
+ return hook.type === "command" && (hook.async === true || hook.asyncRewake === true);
106977
+ }
106978
+ function hookGate(hook, event, matcher, dir) {
106979
+ const key = hook.once ? hookIdentity(event, hook, matcher, dir) : void 0;
106980
+ if (key && _hookOnceDone.has(key))
106981
+ return { skip: true };
106982
+ if (isBackgroundHook(hook))
106983
+ return { background: true, key };
106984
+ return { key, statusId: hook.statusMessage ? hookStatusBegin(hook.statusMessage) : void 0 };
106985
+ }
106986
+ function startBackgroundHook(hook, payload, opts) {
106987
+ const statusId = hook.statusMessage ? hookStatusBegin(hook.statusMessage) : void 0;
106988
+ const done = (r2) => {
106989
+ if (statusId !== void 0)
106990
+ hookStatusEnd(statusId);
106991
+ if (r2.status === 0 && opts.key)
106992
+ _hookOnceDone.add(opts.key);
106993
+ const out = (r2.stdout ?? "").toString().trim();
106994
+ if (hook.asyncRewake && r2.status === 2) {
106995
+ const text = (r2.stderr ?? "").toString().trim() || out;
106996
+ _hookDeliveries.push({ kind: "wake", text: text || "Hook asked the session to wake (exit 2).", source: opts.eventLabel });
106997
+ try {
106998
+ _hookWakeListener?.();
106999
+ } catch {
107000
+ }
107001
+ return;
107002
+ }
107003
+ const context = directiveContext(out);
107004
+ if (context)
107005
+ _hookDeliveries.push({ kind: "context", text: context, source: opts.eventLabel });
107006
+ };
107007
+ void runHookHandler(hook, payload, { cwd: opts.cwd, timeoutMs: opts.timeoutMs, env: opts.env, event: opts.event }).then(done, () => done({ status: null, stdout: "", stderr: "" }));
107008
+ }
107009
+ function directiveContext(out) {
107010
+ if (!out.startsWith("{"))
107011
+ return "";
107012
+ try {
107013
+ const j = JSON.parse(out);
107014
+ const parts = [];
107015
+ if (typeof j.additionalContext === "string" && j.additionalContext)
107016
+ parts.push(j.additionalContext);
107017
+ if (typeof j.systemMessage === "string" && j.systemMessage)
107018
+ parts.push(j.systemMessage);
107019
+ return parts.join("\n");
107020
+ } catch {
107021
+ return "";
107022
+ }
107023
+ }
106803
107024
  function spawnHook(command, opts) {
106804
107025
  return new Promise((resolve12) => {
106805
107026
  let stdout = "";
@@ -106809,7 +107030,8 @@ var require_hooks = __commonJS({
106809
107030
  if (settled)
106810
107031
  return;
106811
107032
  settled = true;
106812
- clearTimeout(timer);
107033
+ if (timer)
107034
+ clearTimeout(timer);
106813
107035
  resolve12({ status, stdout, stderr });
106814
107036
  };
106815
107037
  let child;
@@ -106819,7 +107041,7 @@ var require_hooks = __commonJS({
106819
107041
  resolve12({ status: null, stdout: "", stderr: "" });
106820
107042
  return;
106821
107043
  }
106822
- const timer = setTimeout(() => {
107044
+ const timer = opts.timeout === null ? null : setTimeout(() => {
106823
107045
  try {
106824
107046
  child.kill("SIGTERM");
106825
107047
  } catch {
@@ -106828,12 +107050,16 @@ var require_hooks = __commonJS({
106828
107050
  }, opts.timeout);
106829
107051
  const cap = 16 * 1024 * 1024;
106830
107052
  child.stdout?.on("data", (d) => {
107053
+ const s2 = d.toString("utf-8");
106831
107054
  if (stdout.length < cap)
106832
- stdout += d.toString("utf-8");
107055
+ stdout += s2;
107056
+ opts.onData?.({ stdout: s2 });
106833
107057
  });
106834
107058
  child.stderr?.on("data", (d) => {
107059
+ const s2 = d.toString("utf-8");
106835
107060
  if (stderr.length < cap)
106836
- stderr += d.toString("utf-8");
107061
+ stderr += s2;
107062
+ opts.onData?.({ stderr: s2 });
106837
107063
  });
106838
107064
  child.on("error", () => done(null));
106839
107065
  child.on("close", (code) => done(code));
@@ -106866,13 +107092,91 @@ var require_hooks = __commonJS({
106866
107092
  return { status: 1, stdout: "", stderr: `hook request failed: ${err.message}` };
106867
107093
  }
106868
107094
  }
106869
- async function runHookHandler(hook, payload, opts) {
106870
- if (hook.type === "http")
106871
- return httpHook(hook, payload, opts.timeoutMs);
106872
- if (hook.type === "prompt" || hook.type === "agent") {
106873
- return promptHook(hook, payload, { ...opts, envCtx: opts.hookEnv });
107095
+ async function mcpToolHook(hook, payload) {
107096
+ const caller = _hookMcpCaller;
107097
+ const server = String(hook.server ?? "");
107098
+ const tool = String(hook.tool ?? "");
107099
+ if (!caller) {
107100
+ return { status: 1, stdout: "", stderr: `mcp_tool hook "${server}:${tool}" needs a connected MCP client` };
107101
+ }
107102
+ try {
107103
+ const res = await caller(server, tool, mcpHookInput(hook.input, payload));
107104
+ if ("error" in res)
107105
+ return { status: 1, stdout: "", stderr: res.error };
107106
+ const text = (res.text ?? "").trim();
107107
+ if (!text)
107108
+ return { status: 0, stdout: "", stderr: "" };
107109
+ return { status: 0, stdout: text.startsWith("{") ? text : JSON.stringify({ additionalContext: text }), stderr: "" };
107110
+ } catch (err) {
107111
+ return { status: 1, stdout: "", stderr: `mcp_tool hook ${server}:${tool} failed: ${err.message}` };
106874
107112
  }
106875
- return spawnHook(String(hook.command ?? ""), { cwd: opts.cwd, timeout: opts.timeoutMs, input: payload, env: opts.env });
107113
+ }
107114
+ function mcpHookInput(input, payload) {
107115
+ if (!input)
107116
+ return {};
107117
+ let root = {};
107118
+ try {
107119
+ const j = JSON.parse(payload);
107120
+ if (j && typeof j === "object" && !Array.isArray(j))
107121
+ root = j;
107122
+ } catch {
107123
+ }
107124
+ const nav = (parts) => {
107125
+ let cur = root;
107126
+ for (const p of parts) {
107127
+ if (cur === null || typeof cur !== "object")
107128
+ return void 0;
107129
+ cur = cur[p];
107130
+ }
107131
+ return cur;
107132
+ };
107133
+ const lookup = (path33) => {
107134
+ const parts = path33.split(".").map((s2) => s2.trim()).filter(Boolean);
107135
+ if (!parts.length)
107136
+ return void 0;
107137
+ const candidates = [parts];
107138
+ if (parts[0] === "tool_input" || parts[0] === "input") {
107139
+ const rest = parts.slice(1);
107140
+ candidates.unshift(rest, ["input", ...rest], ["tool_input", ...rest]);
107141
+ }
107142
+ for (const c of candidates) {
107143
+ const v = nav(c);
107144
+ if (v !== void 0)
107145
+ return v;
107146
+ }
107147
+ return void 0;
107148
+ };
107149
+ const subst = (v) => {
107150
+ if (typeof v !== "string")
107151
+ return v;
107152
+ const whole = v.match(/^\$\{([^}]+)\}$/);
107153
+ if (whole) {
107154
+ const hit = lookup(whole[1]);
107155
+ return hit === void 0 ? "" : hit;
107156
+ }
107157
+ return v.replace(/\$\{([^}]+)\}/g, (_m, p) => {
107158
+ const hit = lookup(p);
107159
+ if (hit === void 0)
107160
+ return "";
107161
+ return typeof hit === "string" ? hit : JSON.stringify(hit);
107162
+ });
107163
+ };
107164
+ const out = {};
107165
+ for (const [k, val] of Object.entries(input))
107166
+ out[k] = subst(val);
107167
+ return out;
107168
+ }
107169
+ async function runHookHandler(hook, payload, opts) {
107170
+ return runWithHookEvents(hook, opts.event ?? "hook", (onData) => {
107171
+ if (hook.type === "http")
107172
+ return httpHook(hook, payload, opts.timeoutMs ?? 6e4);
107173
+ if (hook.type === "mcp_tool")
107174
+ return mcpToolHook(hook, payload);
107175
+ if (hook.type === "prompt" || hook.type === "agent") {
107176
+ return promptHook(hook, payload, { ...opts, timeoutMs: opts.timeoutMs ?? 6e4, envCtx: opts.hookEnv });
107177
+ }
107178
+ return spawnHook(String(hook.command ?? ""), { cwd: opts.cwd, timeout: opts.timeoutMs, input: payload, env: opts.env, onData });
107179
+ });
106876
107180
  }
106877
107181
  var HOOK_AGENT_TOOLS = [
106878
107182
  {
@@ -107094,22 +107398,38 @@ ${out.output}` : `Error: ${out.error}` : out.output ?? "",
107094
107398
  input,
107095
107399
  ...result ? { result: { output: result.output, error: result.error } } : {}
107096
107400
  });
107401
+ const eventLabel = `${phase} (${toolName})`;
107402
+ const hookEnv = {
107403
+ ...process.env,
107404
+ NEXRALL_TOOL_NAME: toolName,
107405
+ NEXRALL_TOOL_INPUT: JSON.stringify(input),
107406
+ NEXRALL_HOOK_PHASE: phase
107407
+ };
107097
107408
  for (const entry of entries) {
107098
107409
  if (!hookMatches(entry.matcher, toolName))
107099
107410
  continue;
107100
107411
  for (const hook of entry.hooks ?? []) {
107101
107412
  if (!isRunnableHook(hook))
107102
107413
  continue;
107103
- const hookTimeout = hookTimeoutMs(hook, 6e4);
107414
+ const gate = hookGate(hook, phase, entry.matcher, workDir);
107415
+ if (gate.skip)
107416
+ continue;
107417
+ if (gate.background) {
107418
+ startBackgroundHook(hook, payload, {
107419
+ cwd: workDir,
107420
+ env: hookEnv,
107421
+ eventLabel,
107422
+ event: phase,
107423
+ timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 6e4) : null,
107424
+ key: gate.key
107425
+ });
107426
+ continue;
107427
+ }
107104
107428
  const r2 = await runHookHandler(hook, payload, {
107105
107429
  cwd: workDir,
107106
- timeoutMs: hookTimeout,
107107
- env: {
107108
- ...process.env,
107109
- NEXRALL_TOOL_NAME: toolName,
107110
- NEXRALL_TOOL_INPUT: JSON.stringify(input),
107111
- NEXRALL_HOOK_PHASE: phase
107112
- },
107430
+ timeoutMs: hookTimeoutMs(hook, 6e4),
107431
+ env: hookEnv,
107432
+ event: phase,
107113
107433
  ...hookOpts
107114
107434
  });
107115
107435
  const out = (r2.stdout ?? "").toString().trim();
@@ -107131,17 +107451,33 @@ ${out.output}` : `Error: ${out.error}` : out.output ?? "",
107131
107451
  const err = (r2.stderr ?? "").toString().trim();
107132
107452
  outcome.reason = err || outcome.reason || `Blocked by ${phase} hook`;
107133
107453
  }
107454
+ if (gate.statusId !== void 0)
107455
+ hookStatusEnd(gate.statusId);
107456
+ if (r2.status === 0 && gate.key)
107457
+ _hookOnceDone.add(gate.key);
107134
107458
  }
107135
107459
  }
107136
107460
  return outcome;
107137
107461
  }
107138
- async function runSimpleHooks(defs, workDir, extraEnv, hookOpts) {
107462
+ async function runSimpleHooks(defs, workDir, extraEnv, hookOpts, event = "hook") {
107139
107463
  if (!defs?.length)
107140
107464
  return;
107465
+ const env4 = extraEnv ? { ...process.env, ...extraEnv } : void 0;
107141
107466
  for (const hook of defs) {
107142
107467
  if (!isRunnableHook(hook))
107143
107468
  continue;
107144
- await runHookHandler(hook, "", { cwd: workDir, timeoutMs: hookTimeoutMs(hook, 1e4), env: extraEnv ? { ...process.env, ...extraEnv } : void 0, ...hookOpts });
107469
+ const gate = hookGate(hook, event, void 0, workDir);
107470
+ if (gate.skip)
107471
+ continue;
107472
+ if (gate.background) {
107473
+ startBackgroundHook(hook, "", { cwd: workDir, env: env4, eventLabel: event, event, timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 1e4) : null, key: gate.key });
107474
+ continue;
107475
+ }
107476
+ const r2 = await runHookHandler(hook, "", { cwd: workDir, timeoutMs: hookTimeoutMs(hook, 1e4), env: env4, event, ...hookOpts });
107477
+ if (gate.statusId !== void 0)
107478
+ hookStatusEnd(gate.statusId);
107479
+ if (r2.status === 0 && gate.key)
107480
+ _hookOnceDone.add(gate.key);
107145
107481
  }
107146
107482
  }
107147
107483
  function flattenHookDefs(items, matchValue) {
@@ -107158,7 +107494,7 @@ ${out.output}` : `Error: ${out.error}` : out.output ?? "",
107158
107494
  return out;
107159
107495
  }
107160
107496
  function isKnownHandlerType(t2) {
107161
- return t2 === "command" || t2 === "http" || t2 === "prompt" || t2 === "agent";
107497
+ return t2 === "command" || t2 === "http" || t2 === "prompt" || t2 === "agent" || t2 === "mcp_tool";
107162
107498
  }
107163
107499
  function isRunnableHook(hook) {
107164
107500
  if (!isKnownHandlerType(hook.type))
@@ -107167,6 +107503,8 @@ ${out.output}` : `Error: ${out.error}` : out.output ?? "",
107167
107503
  return !!hook.command;
107168
107504
  if (hook.type === "http")
107169
107505
  return !!hook.url;
107506
+ if (hook.type === "mcp_tool")
107507
+ return !!hook.server && !!hook.tool;
107170
107508
  return !!hook.prompt;
107171
107509
  }
107172
107510
  async function runLifecycleHooks(items, event, workDir, payload, matchValue, hookOpts) {
@@ -107178,8 +107516,22 @@ ${out.output}` : `Error: ${out.error}` : out.output ?? "",
107178
107516
  for (const hook of defs) {
107179
107517
  if (!isRunnableHook(hook))
107180
107518
  continue;
107519
+ const gate = hookGate(hook, event, matchValue, workDir);
107520
+ if (gate.skip)
107521
+ continue;
107522
+ if (gate.background) {
107523
+ startBackgroundHook(hook, input, {
107524
+ cwd: workDir,
107525
+ env: { ...process.env, NEXRALL_HOOK_PHASE: event },
107526
+ eventLabel: event,
107527
+ event,
107528
+ timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 6e4) : null,
107529
+ key: gate.key
107530
+ });
107531
+ continue;
107532
+ }
107181
107533
  const timeout = hookTimeoutMs(hook, hook.type === "agent" ? 6e4 : hook.type === "prompt" ? 3e4 : 3e4);
107182
- const r2 = await runHookHandler(hook, input, { cwd: workDir, timeoutMs: timeout, env: { ...process.env, NEXRALL_HOOK_PHASE: event }, ...hookOpts });
107534
+ const r2 = await runHookHandler(hook, input, { cwd: workDir, timeoutMs: timeout, env: { ...process.env, NEXRALL_HOOK_PHASE: event }, event, ...hookOpts });
107183
107535
  const out = (r2.stdout ?? "").toString().trim();
107184
107536
  if (out.startsWith("{")) {
107185
107537
  try {
@@ -107212,6 +107564,10 @@ ${out.output}` : `Error: ${out.error}` : out.output ?? "",
107212
107564
  outcome.block = true;
107213
107565
  outcome.reason = (r2.stderr ?? "").toString().trim() || outcome.reason || `Blocked by ${event} hook`;
107214
107566
  }
107567
+ if (gate.statusId !== void 0)
107568
+ hookStatusEnd(gate.statusId);
107569
+ if (r2.status === 0 && gate.key)
107570
+ _hookOnceDone.add(gate.key);
107215
107571
  }
107216
107572
  return outcome;
107217
107573
  }
@@ -107229,6 +107585,106 @@ ${out.output}` : `Error: ${out.error}` : out.output ?? "",
107229
107585
  }
107230
107586
  return { answer: null };
107231
107587
  }
107588
+ function parseElicitationAnswer(stdout) {
107589
+ const out = stdout.trim();
107590
+ if (!out.startsWith("{"))
107591
+ return null;
107592
+ try {
107593
+ const j = JSON.parse(out);
107594
+ const src = j.hookSpecificOutput ?? j;
107595
+ if (src.action !== "accept" && src.action !== "decline" && src.action !== "cancel")
107596
+ return null;
107597
+ const content = src.content && typeof src.content === "object" && !Array.isArray(src.content) ? src.content : void 0;
107598
+ return content ? { action: src.action, content } : { action: src.action };
107599
+ } catch {
107600
+ return null;
107601
+ }
107602
+ }
107603
+ async function runElicitationHooks2(workDir, server, input, sessionId = "") {
107604
+ try {
107605
+ const items = loadHooks2(workDir).Elicitation;
107606
+ if (!items?.length)
107607
+ return null;
107608
+ const defs = flattenHookDefs(items, server);
107609
+ if (!defs.length)
107610
+ return null;
107611
+ const payload = JSON.stringify({ session_id: sessionId, cwd: workDir, hook_event_name: "Elicitation", mcp_server_name: server, ...input });
107612
+ for (const hook of defs) {
107613
+ if (!isRunnableHook(hook))
107614
+ continue;
107615
+ const gate = hookGate(hook, "Elicitation", server, workDir);
107616
+ if (gate.skip)
107617
+ continue;
107618
+ if (gate.background) {
107619
+ startBackgroundHook(hook, payload, { cwd: workDir, env: { ...process.env, NEXRALL_HOOK_PHASE: "Elicitation" }, eventLabel: "Elicitation", event: "Elicitation", timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 6e4) : null, key: gate.key });
107620
+ continue;
107621
+ }
107622
+ const r2 = await runHookHandler(hook, payload, { cwd: workDir, timeoutMs: hookTimeoutMs(hook, 3e4), env: { ...process.env, NEXRALL_HOOK_PHASE: "Elicitation" }, event: "Elicitation" });
107623
+ if (gate.statusId !== void 0)
107624
+ hookStatusEnd(gate.statusId);
107625
+ if (r2.status === 0 && gate.key)
107626
+ _hookOnceDone.add(gate.key);
107627
+ if (r2.status === 2)
107628
+ return { action: "decline" };
107629
+ const answer = parseElicitationAnswer((r2.stdout ?? "").toString());
107630
+ if (answer)
107631
+ return answer;
107632
+ }
107633
+ } catch {
107634
+ }
107635
+ return null;
107636
+ }
107637
+ async function runElicitationResultHooks2(workDir, server, answer, meta = {}) {
107638
+ try {
107639
+ const items = loadHooks2(workDir).ElicitationResult;
107640
+ if (!items?.length)
107641
+ return answer;
107642
+ const defs = flattenHookDefs(items, server);
107643
+ if (!defs.length)
107644
+ return answer;
107645
+ let current = answer;
107646
+ for (const hook of defs) {
107647
+ if (!isRunnableHook(hook))
107648
+ continue;
107649
+ const gate = hookGate(hook, "ElicitationResult", server, workDir);
107650
+ if (gate.skip)
107651
+ continue;
107652
+ const payload = JSON.stringify({
107653
+ session_id: meta.sessionId ?? "",
107654
+ cwd: workDir,
107655
+ hook_event_name: "ElicitationResult",
107656
+ mcp_server_name: server,
107657
+ action: current.action,
107658
+ content: current.content,
107659
+ mode: meta.mode,
107660
+ elicitation_id: meta.elicitationId
107661
+ });
107662
+ if (gate.background) {
107663
+ startBackgroundHook(hook, payload, { cwd: workDir, env: { ...process.env, NEXRALL_HOOK_PHASE: "ElicitationResult" }, eventLabel: "ElicitationResult", event: "ElicitationResult", timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 6e4) : null, key: gate.key });
107664
+ continue;
107665
+ }
107666
+ const r2 = await runHookHandler(hook, payload, { cwd: workDir, timeoutMs: hookTimeoutMs(hook, 3e4), env: { ...process.env, NEXRALL_HOOK_PHASE: "ElicitationResult" }, event: "ElicitationResult" });
107667
+ if (gate.statusId !== void 0)
107668
+ hookStatusEnd(gate.statusId);
107669
+ if (r2.status === 0 && gate.key)
107670
+ _hookOnceDone.add(gate.key);
107671
+ if (r2.status === 2)
107672
+ return { action: "decline" };
107673
+ const override = parseElicitationAnswer((r2.stdout ?? "").toString());
107674
+ if (override) {
107675
+ if (override.action === "accept") {
107676
+ const content = override.content ?? (current.action === "accept" ? current.content : void 0);
107677
+ current = content ? { action: "accept", content } : { action: "accept" };
107678
+ } else {
107679
+ current = { action: override.action };
107680
+ }
107681
+ }
107682
+ }
107683
+ return current;
107684
+ } catch {
107685
+ return answer;
107686
+ }
107687
+ }
107232
107688
  async function fireObserverHook3(workDir, event, payload, matchValue) {
107233
107689
  try {
107234
107690
  const items = loadHooks2(workDir)[event];
@@ -107923,6 +108379,11 @@ var require_iterationPolicy = __commonJS({
107923
108379
  \u{1F6D1} Stopped: the same tool error repeated ${exports2.REPEAT_STALL_LIMIT} rounds in a row, so the agent was looping without making progress.` + (ctx.repeatError ? ` The recurring error was:
107924
108380
  ${ctx.repeatError}
107925
108381
  ` : "\n") + `Fix that underlying cause (or grant the needed permission) and send "continue".
108382
+ `;
108383
+ case "hook-blocked":
108384
+ return `
108385
+ \u26D4 A PostToolBatch hook stopped the turn before the next model call` + (ctx.hookReason ? `: ${ctx.hookReason}` : ".") + `
108386
+ The reason is in the conversation, so send "continue" once the cause is handled.
107926
108387
  `;
107927
108388
  case "budget":
107928
108389
  return `
@@ -108523,7 +108984,10 @@ ${body}`);
108523
108984
  "no-balance",
108524
108985
  "no-team-budget",
108525
108986
  "team-unavailable",
108526
- "reported-elsewhere"
108987
+ "reported-elsewhere",
108988
+ // A hook stopping the batch means the sub-agent stopped short of its report, so the
108989
+ // parent must not read the result as a finished one.
108990
+ "hook-blocked"
108527
108991
  ]);
108528
108992
  exports2.SUBAGENT_PREAMBLE = [
108529
108993
  "# You are a sub-agent",
@@ -109232,8 +109696,8 @@ var require_loop = __commonJS({
109232
109696
  "../core/dist/agent/loop.js"(exports2) {
109233
109697
  "use strict";
109234
109698
  Object.defineProperty(exports2, "__esModule", { value: true });
109235
- exports2.resolveSubtaskTimeoutMs = exports2.canSpawnSubAgents = exports2.intersectAllowlists = exports2.noSpawnReason = exports2.resolveMaxSubagentDepth = exports2._sessionSubAgentCount = exports2.resetSessionSubAgentBudget = exports2._resetSubTaskLimiter = exports2.createLimiter = exports2.resolveMaxSubagentsPerSession = exports2.resolveMaxConcurrentSubtasks = exports2.lockPathsFor = exports2.resolveMaxIterations = exports2.stopReasonNotice = exports2.executeAgentMemoryWrite = exports2.AGENT_MEMORY_TOOL_SCHEMA = exports2.AGENT_MEMORY_TOOL = exports2._stallLimits = exports2.errorRoundSignature = exports2._emptyTurnRetry = exports2.shouldRetryEmptyTurn = exports2.emptyTurnBackoffMs = exports2.compactMessagesForResume = exports2.estimateTokensRough = exports2.makeCachedSummarizer = exports2.pruneOldToolResults = exports2.ledgerSummary = exports2.ledgerRecord = exports2.createLedger = exports2.transcriptOf = exports2.persistRuntimeContext = exports2.findSafeCutIndex = exports2.VERIFY_CMD_RE = exports2.allowsTestOnlyWrite = exports2.WRITE_TOOL_NAMES = exports2.estimateBodyBytes = exports2.pruneReclaimFloor = exports2.compactionThresholds = exports2.compactionLimits = exports2.contextWindowFor = exports2.fireObserverHook = exports2.fireManualPreCompactHook = exports2.fireManualPostCompactHook = exports2.fireNotificationHooks = exports2.runPermissionRequestHooks = exports2.runLifecycleHooks = exports2.classifyStopFailure = exports2.parsePromptHookReply = exports2.loadHooks = exports2.bashNeedsRepoLock = void 0;
109236
- exports2.lastToolResults = exports2.summariseSubTaskProgress = exports2.capSubTaskText = exports2.extractSubTaskText = exports2.ToolNotAllowedError = void 0;
109699
+ exports2.resolveMaxIterations = exports2.stopReasonNotice = exports2.executeAgentMemoryWrite = exports2.AGENT_MEMORY_TOOL_SCHEMA = exports2.AGENT_MEMORY_TOOL = exports2._stallLimits = exports2.errorRoundSignature = exports2._emptyTurnRetry = exports2.shouldRetryEmptyTurn = exports2.emptyTurnBackoffMs = exports2.compactMessagesForResume = exports2.estimateTokensRough = exports2.makeCachedSummarizer = exports2.pruneOldToolResults = exports2.ledgerSummary = exports2.ledgerRecord = exports2.createLedger = exports2.transcriptOf = exports2.persistRuntimeContext = exports2.findSafeCutIndex = exports2.VERIFY_CMD_RE = exports2.allowsTestOnlyWrite = exports2.WRITE_TOOL_NAMES = exports2.estimateBodyBytes = exports2.pruneReclaimFloor = exports2.compactionThresholds = exports2.compactionLimits = exports2.contextWindowFor = exports2.runElicitationResultHooks = exports2.runElicitationHooks = exports2.hasHookEventListener = exports2.setHookEventListener = exports2.resetHookOnceState = exports2.setHookMcpCaller = exports2.setHookStatusListener = exports2.setHookWakeListener = exports2.formatHookDeliveries = exports2.hasPendingHookWake = exports2.takeHookWakes = exports2.drainHookDeliveries = exports2.fireObserverHook = exports2.fireManualPreCompactHook = exports2.fireManualPostCompactHook = exports2.fireNotificationHooks = exports2.runPermissionRequestHooks = exports2.runLifecycleHooks = exports2.classifyStopFailure = exports2.parsePromptHookReply = exports2.loadHooks = exports2.bashNeedsRepoLock = void 0;
109700
+ exports2.lastToolResults = exports2.summariseSubTaskProgress = exports2.capSubTaskText = exports2.extractSubTaskText = exports2.ToolNotAllowedError = exports2.resolveSubtaskTimeoutMs = exports2.canSpawnSubAgents = exports2.intersectAllowlists = exports2.noSpawnReason = exports2.resolveMaxSubagentDepth = exports2._sessionSubAgentCount = exports2.resetSessionSubAgentBudget = exports2._resetSubTaskLimiter = exports2.createLimiter = exports2.resolveMaxSubagentsPerSession = exports2.resolveMaxConcurrentSubtasks = exports2.lockPathsFor = void 0;
109237
109701
  exports2.dispatchSubAgent = dispatchSubAgent;
109238
109702
  exports2.dispatchBackgroundSubAgent = dispatchBackgroundSubAgent;
109239
109703
  exports2.runAgentLoop = runAgentLoop2;
@@ -109244,6 +109708,7 @@ var require_loop = __commonJS({
109244
109708
  var types_1 = require_types();
109245
109709
  var client_1 = require_client();
109246
109710
  var executor_1 = require_executor();
109711
+ var sharedTasks_1 = require_sharedTasks();
109247
109712
  var agentTypes_1 = require_agentTypes();
109248
109713
  var skills_1 = require_skills();
109249
109714
  var rules_1 = require_rules();
@@ -109291,6 +109756,42 @@ var require_loop = __commonJS({
109291
109756
  Object.defineProperty(exports2, "fireObserverHook", { enumerable: true, get: function() {
109292
109757
  return hooks_2.fireObserverHook;
109293
109758
  } });
109759
+ Object.defineProperty(exports2, "drainHookDeliveries", { enumerable: true, get: function() {
109760
+ return hooks_2.drainHookDeliveries;
109761
+ } });
109762
+ Object.defineProperty(exports2, "takeHookWakes", { enumerable: true, get: function() {
109763
+ return hooks_2.takeHookWakes;
109764
+ } });
109765
+ Object.defineProperty(exports2, "hasPendingHookWake", { enumerable: true, get: function() {
109766
+ return hooks_2.hasPendingHookWake;
109767
+ } });
109768
+ Object.defineProperty(exports2, "formatHookDeliveries", { enumerable: true, get: function() {
109769
+ return hooks_2.formatHookDeliveries;
109770
+ } });
109771
+ Object.defineProperty(exports2, "setHookWakeListener", { enumerable: true, get: function() {
109772
+ return hooks_2.setHookWakeListener;
109773
+ } });
109774
+ Object.defineProperty(exports2, "setHookStatusListener", { enumerable: true, get: function() {
109775
+ return hooks_2.setHookStatusListener;
109776
+ } });
109777
+ Object.defineProperty(exports2, "setHookMcpCaller", { enumerable: true, get: function() {
109778
+ return hooks_2.setHookMcpCaller;
109779
+ } });
109780
+ Object.defineProperty(exports2, "resetHookOnceState", { enumerable: true, get: function() {
109781
+ return hooks_2.resetHookOnceState;
109782
+ } });
109783
+ Object.defineProperty(exports2, "setHookEventListener", { enumerable: true, get: function() {
109784
+ return hooks_2.setHookEventListener;
109785
+ } });
109786
+ Object.defineProperty(exports2, "hasHookEventListener", { enumerable: true, get: function() {
109787
+ return hooks_2.hasHookEventListener;
109788
+ } });
109789
+ Object.defineProperty(exports2, "runElicitationHooks", { enumerable: true, get: function() {
109790
+ return hooks_2.runElicitationHooks;
109791
+ } });
109792
+ Object.defineProperty(exports2, "runElicitationResultHooks", { enumerable: true, get: function() {
109793
+ return hooks_2.runElicitationResultHooks;
109794
+ } });
109294
109795
  var compaction_1 = require_compaction();
109295
109796
  var compaction_2 = require_compaction();
109296
109797
  Object.defineProperty(exports2, "contextWindowFor", { enumerable: true, get: function() {
@@ -109465,6 +109966,27 @@ var require_loop = __commonJS({
109465
109966
  subAgentBudget_1._inFlightByDepth.delete(childDepth);
109466
109967
  }
109467
109968
  }
109969
+ async function taskHookVeto(entries, event, input, options, extra = {}) {
109970
+ if (!entries?.length)
109971
+ return null;
109972
+ const taskId = typeof input.task_id === "string" ? input.task_id : "";
109973
+ const task = extra.task_subject ? null : (0, sharedTasks_1.listSharedTasks)(options.workDir).find((t2) => t2.id === taskId);
109974
+ const payload = {
109975
+ session_id: options.sessionId ?? "",
109976
+ ...taskId ? { task_id: taskId } : {},
109977
+ ...task ? { task_subject: task.content, task_description: task.content } : {},
109978
+ ...extra,
109979
+ tool_input: input
109980
+ };
109981
+ try {
109982
+ const outcome = await (0, hooks_1.runLifecycleHooks)(entries, event, options.workDir, payload, void 0, (0, hooks_1.hookRunOptsFor)(options));
109983
+ if (!outcome.block)
109984
+ return null;
109985
+ return `Refused by ${event} hook${outcome.reason ? `: ${outcome.reason}` : ""}`;
109986
+ } catch {
109987
+ return null;
109988
+ }
109989
+ }
109468
109990
  function dispatchBackgroundSubAgent(input, options, agentTypes, hub) {
109469
109991
  const overBudget = (0, subAgentBudget_1.claimSessionSubAgentSlot)(options.workDir, options.onNotice ?? options.onText, options.sessionId);
109470
109992
  if (overBudget)
@@ -109668,6 +110190,7 @@ ${ctx}
109668
110190
  let repeatedErrorRounds = 0;
109669
110191
  let lastErrorSignature = "";
109670
110192
  let stalledRepeatError = null;
110193
+ let hookBlockedReason = null;
109671
110194
  let budget = maxIterations;
109672
110195
  let iteration = 0;
109673
110196
  let emptyTurnRetries = 0;
@@ -109851,7 +110374,8 @@ ${ctx}
109851
110374
  const queued = options.takePendingInput?.() ?? [];
109852
110375
  const peerMsgs = options.drainPeerMessages?.() ?? [];
109853
110376
  const bgDone = drainBackgroundNotifications();
109854
- if (queued.length || peerMsgs.length || bgDone.length) {
110377
+ const hookMsgs = (0, hooks_1.drainHookDeliveries)();
110378
+ if (queued.length || peerMsgs.length || bgDone.length || hookMsgs.length) {
109855
110379
  const parts = [];
109856
110380
  if (queued.length) {
109857
110381
  parts.push(queued.join("\n\n"));
@@ -109863,6 +110387,8 @@ ${ctx}
109863
110387
  }
109864
110388
  if (bgDone.length)
109865
110389
  parts.push((0, backgroundAgents_1.formatBackgroundNotifications)(bgDone));
110390
+ if (hookMsgs.length)
110391
+ parts.push((0, hooks_1.formatHookDeliveries)(hookMsgs));
109866
110392
  messages.push({ role: "user", content: [{ type: "text", text: parts.join("\n\n") }] });
109867
110393
  continue;
109868
110394
  }
@@ -109895,7 +110421,8 @@ ${ctx}
109895
110421
  const queued = options.takePendingInput?.() ?? [];
109896
110422
  const peerMsgs = options.drainPeerMessages?.() ?? [];
109897
110423
  const bgDone = drainBackgroundNotifications();
109898
- if (queued.length || peerMsgs.length || bgDone.length) {
110424
+ const hookMsgs = (0, hooks_1.drainHookDeliveries)();
110425
+ if (queued.length || peerMsgs.length || bgDone.length || hookMsgs.length) {
109899
110426
  const parts = [];
109900
110427
  if (queued.length) {
109901
110428
  parts.push(queued.join("\n\n"));
@@ -109907,6 +110434,8 @@ ${ctx}
109907
110434
  }
109908
110435
  if (bgDone.length)
109909
110436
  parts.push((0, backgroundAgents_1.formatBackgroundNotifications)(bgDone));
110437
+ if (hookMsgs.length)
110438
+ parts.push((0, hooks_1.formatHookDeliveries)(hookMsgs));
109910
110439
  messages.push({ role: "user", content: [{ type: "text", text: parts.join("\n\n") }] });
109911
110440
  continue;
109912
110441
  }
@@ -110065,7 +110594,16 @@ ${ctx}
110065
110594
  if (pre.block) {
110066
110595
  result = { error: `Blocked by PreToolUse hook: ${pre.reason}` };
110067
110596
  } else {
110597
+ const taskIdsBefore = name === "create_shared_task" ? new Set((0, sharedTasks_1.listSharedTasks)(options.workDir).map((t2) => t2.id)) : null;
110068
110598
  try {
110599
+ if (name === "update_shared_task_status" && String(input.status ?? "") === "completed") {
110600
+ const veto = await taskHookVeto(hooks.TaskCompleted, "TaskCompleted", input, options);
110601
+ if (veto) {
110602
+ result = { error: veto };
110603
+ options.onToolResult(name, result, void 0, toolMeta);
110604
+ return { block: { ...block, id }, result };
110605
+ }
110606
+ }
110069
110607
  if (name === iterationPolicy_1.AGENT_MEMORY_TOOL) {
110070
110608
  result = await (0, iterationPolicy_1.executeAgentMemoryWrite)(input, options._agentMemory, options.workDir);
110071
110609
  options.onToolResult(name, result, void 0, toolMeta);
@@ -110103,6 +110641,21 @@ ${ctx}
110103
110641
  } catch (err) {
110104
110642
  result = { error: `Tool execution failed: ${err.message}` };
110105
110643
  }
110644
+ if (name === "create_shared_task" && result.error === void 0 && taskIdsBefore) {
110645
+ const created = (0, sharedTasks_1.listSharedTasks)(options.workDir).filter((t2) => !taskIdsBefore.has(t2.id) && typeof input.content === "string" && t2.content === input.content);
110646
+ const task = created[created.length - 1];
110647
+ if (task) {
110648
+ const veto = await taskHookVeto(hooks.TaskCreated, "TaskCreated", input, options, {
110649
+ task_id: task.id,
110650
+ task_subject: task.content,
110651
+ task_description: task.content
110652
+ });
110653
+ if (veto) {
110654
+ (0, sharedTasks_1.deleteSharedTask)(options.workDir, task.id);
110655
+ result = { error: veto };
110656
+ }
110657
+ }
110658
+ }
110106
110659
  if (name === "memory_write" && result.error === void 0) {
110107
110660
  const scope = input.scope === "global" ? "global" : "project";
110108
110661
  void (0, compaction_1.maybeCompactMemory)(scope, options);
@@ -110194,6 +110747,32 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
110194
110747
  if (bgFinished.length) {
110195
110748
  toolResultContent.push({ type: "text", text: (0, backgroundAgents_1.formatBackgroundNotifications)(bgFinished) });
110196
110749
  }
110750
+ const hookDeliveries = (0, hooks_1.drainHookDeliveries)();
110751
+ if (hookDeliveries.length) {
110752
+ toolResultContent.push({ type: "text", text: (0, hooks_1.formatHookDeliveries)(hookDeliveries) });
110753
+ }
110754
+ let postBatchStop = null;
110755
+ if (hooks.PostToolBatch?.length) {
110756
+ const batch = await (0, hooks_1.runLifecycleHooks)(hooks.PostToolBatch, "PostToolBatch", options.workDir, {
110757
+ tool_calls: toolResults.map(({ block: b, result: r2 }) => ({
110758
+ tool_name: b.name,
110759
+ tool_input: b.input,
110760
+ tool_use_id: b.id,
110761
+ tool_response: r2.error !== void 0 ? r2.output ? `Error: ${r2.error}
110762
+
110763
+ ${r2.output}` : `Error: ${r2.error}` : r2.output ?? ""
110764
+ }))
110765
+ }, void 0, (0, hooks_1.hookRunOptsFor)(options));
110766
+ if (batch.context)
110767
+ toolResultContent.push({ type: "text", text: batch.context });
110768
+ if (batch.block) {
110769
+ postBatchStop = batch.reason?.trim() || "a PostToolBatch hook stopped the turn";
110770
+ toolResultContent.push({
110771
+ type: "text",
110772
+ text: `A PostToolBatch hook stopped the turn before the next model call: ${postBatchStop}`
110773
+ });
110774
+ }
110775
+ }
110197
110776
  const toolResultMessage = {
110198
110777
  role: "user",
110199
110778
  content: toolResultContent
@@ -110201,6 +110780,11 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
110201
110780
  messages.push(toolResultMessage);
110202
110781
  completedRounds++;
110203
110782
  options.onProgress?.(messages);
110783
+ if (postBatchStop) {
110784
+ stopReason = "hook-blocked";
110785
+ hookBlockedReason = postBatchStop;
110786
+ break;
110787
+ }
110204
110788
  const errored = toolResults.filter(({ result }) => result.error !== void 0);
110205
110789
  const allErrored = toolResults.length > 0 && errored.length === toolResults.length;
110206
110790
  consecutiveErrorRounds = allErrored ? consecutiveErrorRounds + 1 : 0;
@@ -110234,7 +110818,7 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
110234
110818
  const apiFailure = (0, hooks_1.apiFailureForStopReason)(stopReason);
110235
110819
  if (apiFailure)
110236
110820
  await fireStopFailure(apiFailure);
110237
- const notice = (0, iterationPolicy_1.stopReasonNotice)(stopReason, { budget, repeatError: stalledRepeatError });
110821
+ const notice = (0, iterationPolicy_1.stopReasonNotice)(stopReason, { budget, repeatError: stalledRepeatError, hookReason: hookBlockedReason });
110238
110822
  if (options._onStopReason)
110239
110823
  options._onStopReason(stopReason, notice || null);
110240
110824
  else if (notice)
@@ -110525,9 +111109,12 @@ var require_client2 = __commonJS({
110525
111109
  "../core/dist/mcp/client.js"(exports2) {
110526
111110
  "use strict";
110527
111111
  Object.defineProperty(exports2, "__esModule", { value: true });
110528
- exports2.McpClient = void 0;
111112
+ exports2.McpClient = exports2.MCP_ELICITATION_CAPABILITY = void 0;
110529
111113
  exports2.flattenResourceContents = flattenResourceContents;
110530
111114
  exports2.flattenPromptMessages = flattenPromptMessages;
111115
+ exports2.elicitationCapability = elicitationCapability;
111116
+ exports2.normalizeElicitationRequest = normalizeElicitationRequest;
111117
+ exports2.serverRequestReply = serverRequestReply;
110531
111118
  var child_process_1 = __require("child_process");
110532
111119
  function flattenResourceContents(result, maxChars = 1e5) {
110533
111120
  const contents = result?.contents ?? [];
@@ -110567,6 +111154,39 @@ var require_client2 = __commonJS({
110567
111154
  return text.length > maxChars ? text.slice(0, maxChars) + `
110568
111155
  \u2026 [truncated, ${text.length - maxChars} more characters]` : text;
110569
111156
  }
111157
+ exports2.MCP_ELICITATION_CAPABILITY = { elicitation: { form: {}, url: {} } };
111158
+ function elicitationCapability(bare) {
111159
+ return bare ? { elicitation: {} } : { elicitation: { form: {}, url: {} } };
111160
+ }
111161
+ function normalizeElicitationRequest(params) {
111162
+ const p = params ?? {};
111163
+ const url = typeof p.url === "string" ? p.url : void 0;
111164
+ const schema = p.requestedSchema ?? p.schema;
111165
+ return {
111166
+ message: typeof p.message === "string" && p.message.trim() ? p.message : "An MCP server is asking for input.",
111167
+ mode: p.mode === "url" || !p.mode && url ? "url" : "form",
111168
+ requestedSchema: schema && typeof schema === "object" ? schema : void 0,
111169
+ url,
111170
+ elicitationId: typeof p.elicitationId === "string" ? p.elicitationId : typeof p.elicitation_id === "string" ? p.elicitation_id : void 0
111171
+ };
111172
+ }
111173
+ async function serverRequestReply(server, handler, method, params) {
111174
+ if (method === "elicitation/create") {
111175
+ let resp = { action: "decline" };
111176
+ const request = normalizeElicitationRequest(params);
111177
+ try {
111178
+ if (handler)
111179
+ resp = await handler(server, request);
111180
+ } catch {
111181
+ resp = { action: "decline" };
111182
+ }
111183
+ const result = { action: resp.action };
111184
+ if (resp.action === "accept" && resp.content)
111185
+ result.content = resp.content;
111186
+ return { result };
111187
+ }
111188
+ return { error: { code: -32601, message: `Method not found: ${method}` } };
111189
+ }
110570
111190
  var McpClient = class {
110571
111191
  constructor(name, config) {
110572
111192
  this.config = config;
@@ -110575,11 +111195,15 @@ var require_client2 = __commonJS({
110575
111195
  this._pending = /* @__PURE__ */ new Map();
110576
111196
  this._buf = "";
110577
111197
  this._tools = [];
111198
+ this._elicitationHandler = null;
110578
111199
  this.name = name;
110579
111200
  }
110580
111201
  get tools() {
110581
111202
  return this._tools;
110582
111203
  }
111204
+ setElicitationHandler(handler) {
111205
+ this._elicitationHandler = handler;
111206
+ }
110583
111207
  async connect() {
110584
111208
  if (!this.config.command)
110585
111209
  throw new Error(`MCP server "${this.name}" has no command (stdio transport requires one).`);
@@ -110607,7 +111231,7 @@ var require_client2 = __commonJS({
110607
111231
  });
110608
111232
  await this._request("initialize", {
110609
111233
  protocolVersion: "2024-11-05",
110610
- capabilities: {},
111234
+ capabilities: elicitationCapability(this.config.bareElicitationCapability),
110611
111235
  clientInfo: { name: "nexrall-code", version: "1.0" }
110612
111236
  });
110613
111237
  this._notify("notifications/initialized");
@@ -110688,6 +111312,10 @@ var require_client2 = __commonJS({
110688
111312
  } catch {
110689
111313
  return;
110690
111314
  }
111315
+ if (typeof msg.method === "string" && msg.id != null) {
111316
+ void this._handleServerRequest(msg.id, msg.method, msg.params);
111317
+ return;
111318
+ }
110691
111319
  if (msg.id == null)
110692
111320
  return;
110693
111321
  const pending = this._pending.get(msg.id);
@@ -110700,6 +111328,11 @@ var require_client2 = __commonJS({
110700
111328
  pending.resolve(msg.result);
110701
111329
  }
110702
111330
  }
111331
+ /** Answer a server→client request. Errors are swallowed — the server must get SOME reply. */
111332
+ async _handleServerRequest(id, method, params) {
111333
+ const reply = await serverRequestReply(this.name, this._elicitationHandler, method, params);
111334
+ this._send({ jsonrpc: "2.0", id, ...reply });
111335
+ }
110703
111336
  _rejectAll(err) {
110704
111337
  for (const { reject } of this._pending.values())
110705
111338
  reject(err);
@@ -110738,17 +111371,21 @@ var require_httpClient = __commonJS({
110738
111371
  this._nextId = 1;
110739
111372
  this._tools = [];
110740
111373
  this._sessionId = null;
111374
+ this._elicitationHandler = null;
110741
111375
  this.name = name;
110742
111376
  }
110743
111377
  get tools() {
110744
111378
  return this._tools;
110745
111379
  }
111380
+ setElicitationHandler(handler) {
111381
+ this._elicitationHandler = handler;
111382
+ }
110746
111383
  async connect() {
110747
111384
  if (!this.config.url)
110748
111385
  throw new Error(`MCP server "${this.name}" has no url (http/sse transport requires one).`);
110749
111386
  await this._rpc("initialize", {
110750
111387
  protocolVersion: "2024-11-05",
110751
- capabilities: {},
111388
+ capabilities: (0, client_1.elicitationCapability)(this.config.bareElicitationCapability),
110752
111389
  clientInfo: { name: "nexrall-code", version: "1.0" }
110753
111390
  });
110754
111391
  await this._notify("notifications/initialized");
@@ -110843,29 +111480,106 @@ var require_httpClient = __commonJS({
110843
111480
  throw new Error(`MCP HTTP ${resp.status} ${resp.statusText}${body ? `: ${body.slice(0, 200)}` : ""}`);
110844
111481
  }
110845
111482
  const contentType = resp.headers.get("content-type") ?? "";
111483
+ if (contentType.includes("text/event-stream"))
111484
+ return await this._consumeSse(resp, id);
110846
111485
  const text = await resp.text();
110847
111486
  if (!text.trim())
110848
111487
  return null;
110849
- const consume = (msg) => {
111488
+ const msg = JSON.parse(text);
111489
+ if (msg.error)
111490
+ throw new Error(`MCP error ${msg.error.code}: ${msg.error.message}`);
111491
+ return msg.result ?? null;
111492
+ }
111493
+ /**
111494
+ * Read an SSE reply frame by frame. A server request (elicitation) is answered on the
111495
+ * spot: the server WAITS for that answer before it sends our own result, so buffering
111496
+ * the whole stream first would deadlock until the request timeout. As soon as our id
111497
+ * arrives we stop and cancel the body (the stream may stay open for the session).
111498
+ * Falls back to a buffered parse when the runtime hands us no readable body.
111499
+ */
111500
+ async _consumeSse(resp, id) {
111501
+ const state = { have: false };
111502
+ const handle2 = (msg) => {
111503
+ if (typeof msg.method === "string" && msg.id != null) {
111504
+ this._answerServerRequest(msg.id, msg.method, msg.params);
111505
+ return;
111506
+ }
111507
+ if (msg.id !== id)
111508
+ return;
110850
111509
  if (msg.error)
110851
- throw new Error(`MCP error ${msg.error.code}: ${msg.error.message}`);
110852
- return msg.result ?? null;
111510
+ state.failure = new Error(`MCP error ${msg.error.code}: ${msg.error.message}`);
111511
+ else {
111512
+ state.result = msg.result ?? null;
111513
+ state.have = true;
111514
+ }
110853
111515
  };
110854
- if (contentType.includes("text/event-stream")) {
110855
- for (const frame of text.split(/\r?\n\r?\n/)) {
110856
- const dataLines = frame.split(/\r?\n/).filter((l2) => l2.startsWith("data:")).map((l2) => l2.slice(5).trim());
111516
+ const body = resp.body;
111517
+ if (body && typeof body[Symbol.asyncIterator] === "function") {
111518
+ let buf = "";
111519
+ const dataLines = [];
111520
+ const flush = () => {
110857
111521
  if (!dataLines.length)
111522
+ return;
111523
+ const data = dataLines.join("\n");
111524
+ dataLines.length = 0;
111525
+ try {
111526
+ handle2(JSON.parse(data));
111527
+ } catch {
111528
+ }
111529
+ };
111530
+ try {
111531
+ for await (const chunk of body) {
111532
+ buf += Buffer.from(chunk).toString("utf-8");
111533
+ let nl;
111534
+ while ((nl = buf.indexOf("\n")) !== -1) {
111535
+ let line = buf.slice(0, nl);
111536
+ buf = buf.slice(nl + 1);
111537
+ if (line.endsWith("\r"))
111538
+ line = line.slice(0, -1);
111539
+ if (!line) {
111540
+ flush();
111541
+ if (state.have || state.failure)
111542
+ break;
111543
+ continue;
111544
+ }
111545
+ if (line.startsWith("data:"))
111546
+ dataLines.push(line.slice(5).replace(/^ /, ""));
111547
+ }
111548
+ if (state.have || state.failure)
111549
+ break;
111550
+ }
111551
+ flush();
111552
+ } catch {
111553
+ }
111554
+ try {
111555
+ await body.cancel?.();
111556
+ } catch {
111557
+ }
111558
+ } else {
111559
+ const text = await resp.text();
111560
+ for (const frame of text.split(/\r?\n\r?\n/)) {
111561
+ const data = frame.split(/\r?\n/).filter((l2) => l2.startsWith("data:")).map((l2) => l2.slice(5).trim()).join("\n");
111562
+ if (!data)
110858
111563
  continue;
110859
111564
  try {
110860
- const msg = JSON.parse(dataLines.join("\n"));
110861
- if (msg.id === id)
110862
- return consume(msg);
111565
+ handle2(JSON.parse(data));
110863
111566
  } catch {
110864
111567
  }
110865
111568
  }
110866
- throw new Error("No matching JSON-RPC response found in SSE stream");
110867
111569
  }
110868
- return consume(JSON.parse(text));
111570
+ if (state.failure)
111571
+ throw state.failure;
111572
+ if (!state.have)
111573
+ throw new Error("No matching JSON-RPC response found in SSE stream");
111574
+ return state.result;
111575
+ }
111576
+ /** Reply to a server→client request. Fire-and-forget: the in-flight reader must not block. */
111577
+ _answerServerRequest(id, method, params) {
111578
+ void (async () => {
111579
+ const reply = await (0, client_1.serverRequestReply)(this.name, this._elicitationHandler, method, params);
111580
+ await this._post({ jsonrpc: "2.0", id, ...reply });
111581
+ })().catch(() => {
111582
+ });
110869
111583
  }
110870
111584
  };
110871
111585
  exports2.McpHttpClient = McpHttpClient;
@@ -110897,18 +111611,22 @@ var require_sseClient = __commonJS({
110897
111611
  this._closed = false;
110898
111612
  this._pending = /* @__PURE__ */ new Map();
110899
111613
  this._endpointWaiter = null;
111614
+ this._elicitationHandler = null;
110900
111615
  this.name = name;
110901
111616
  }
110902
111617
  get tools() {
110903
111618
  return this._tools;
110904
111619
  }
111620
+ setElicitationHandler(handler) {
111621
+ this._elicitationHandler = handler;
111622
+ }
110905
111623
  async connect() {
110906
111624
  if (!this.config.url)
110907
111625
  throw new Error(`MCP server "${this.name}" has no url (sse transport requires one).`);
110908
111626
  await this._openStream();
110909
111627
  await this._rpc("initialize", {
110910
111628
  protocolVersion: "2024-11-05",
110911
- capabilities: {},
111629
+ capabilities: (0, client_1.elicitationCapability)(this.config.bareElicitationCapability),
110912
111630
  clientInfo: { name: "nexrall-code", version: "1.0" }
110913
111631
  });
110914
111632
  await this._notify("notifications/initialized");
@@ -111061,6 +111779,17 @@ var require_sseClient = __commonJS({
111061
111779
  } catch {
111062
111780
  return;
111063
111781
  }
111782
+ if (typeof msg.method === "string" && msg.id != null) {
111783
+ const method = msg.method;
111784
+ const params = msg.params;
111785
+ const reqId = msg.id;
111786
+ void (async () => {
111787
+ const reply = await (0, client_1.serverRequestReply)(this.name, this._elicitationHandler, method, params);
111788
+ await this._postMessage({ jsonrpc: "2.0", id: reqId, ...reply });
111789
+ })().catch(() => {
111790
+ });
111791
+ return;
111792
+ }
111064
111793
  if (typeof msg.id === "number" && this._pending.has(msg.id)) {
111065
111794
  const { resolve: resolve12, reject, timer } = this._pending.get(msg.id);
111066
111795
  clearTimeout(timer);
@@ -111069,6 +111798,7 @@ var require_sseClient = __commonJS({
111069
111798
  reject(new Error(`MCP error ${msg.error.code}: ${msg.error.message}`));
111070
111799
  else
111071
111800
  resolve12(msg.result ?? null);
111801
+ return;
111072
111802
  }
111073
111803
  }
111074
111804
  _rpc(method, params) {
@@ -111290,6 +112020,17 @@ var require_manager = __commonJS({
111290
112020
  this._needsAuth = /* @__PURE__ */ new Map();
111291
112021
  this._disabled = /* @__PURE__ */ new Set();
111292
112022
  this.stats = new stats_1.ToolStats();
112023
+ this._elicitationHandler = null;
112024
+ }
112025
+ /**
112026
+ * Route every server's elicitation (`elicitation/create`) through this handler —
112027
+ * including servers that connect later, which is why it is also re-applied in
112028
+ * connectAll/enable. Pass null to restore auto-decline.
112029
+ */
112030
+ setElicitationHandler(handler) {
112031
+ this._elicitationHandler = handler;
112032
+ for (const client of this._clients.values())
112033
+ client.setElicitationHandler(handler);
111293
112034
  }
111294
112035
  /** Pick the transport for a server config: stdio (command), Streamable HTTP, or legacy SSE. */
111295
112036
  static makeClient(name, cfg) {
@@ -111335,6 +112076,7 @@ var require_manager = __commonJS({
111335
112076
  return;
111336
112077
  }
111337
112078
  const client = _McpManager.makeClient(name, serverCfg);
112079
+ client.setElicitationHandler(this._elicitationHandler);
111338
112080
  try {
111339
112081
  await client.connect();
111340
112082
  this._clients.set(name, client);
@@ -111594,6 +112336,7 @@ var require_manager = __commonJS({
111594
112336
  return `no server named "${name}" is configured`;
111595
112337
  this._dropClient(name);
111596
112338
  const client = _McpManager.makeClient(name, cfg);
112339
+ client.setElicitationHandler(this._elicitationHandler);
111597
112340
  try {
111598
112341
  await client.connect();
111599
112342
  } catch (err) {
@@ -152779,6 +153522,14 @@ function checkPartialMessages(f3) {
152779
153522
  }
152780
153523
  return null;
152781
153524
  }
153525
+ function checkHookEvents(f3) {
153526
+ if (!f3.includeHookEvents)
153527
+ return null;
153528
+ if (f3.outputFormat !== "stream-json") {
153529
+ return "--include-hook-events needs --output-format stream-json (hook lifecycle events are part of the JSON stream).";
153530
+ }
153531
+ return null;
153532
+ }
152782
153533
  function checkReplayUserMessages(f3) {
152783
153534
  if (!f3.replay)
152784
153535
  return null;
@@ -152809,6 +153560,29 @@ function initEventPayload(i2) {
152809
153560
  function userReplayPayload(text) {
152810
153561
  return { type: "user", message: { role: "user", content: [{ type: "text", text }] } };
152811
153562
  }
153563
+ function hookEventPayload(e2) {
153564
+ const base = {
153565
+ type: "system",
153566
+ subtype: `hook_${e2.kind}`,
153567
+ hook_id: e2.hookId,
153568
+ hook_name: e2.hookName,
153569
+ hook_event: e2.hookEvent,
153570
+ uuid: e2.uuid,
153571
+ session_id: e2.sessionId
153572
+ };
153573
+ if (e2.kind === "started")
153574
+ return base;
153575
+ if (e2.kind === "progress")
153576
+ return { ...base, stdout: e2.stdout ?? "", stderr: e2.stderr ?? "", output: e2.output ?? "" };
153577
+ return {
153578
+ ...base,
153579
+ output: e2.output ?? "",
153580
+ stdout: e2.stdout ?? "",
153581
+ stderr: e2.stderr ?? "",
153582
+ ...e2.exitCode !== void 0 ? { exit_code: e2.exitCode } : {},
153583
+ outcome: e2.outcome ?? "error"
153584
+ };
153585
+ }
152812
153586
  function parseAgentsArg(raw, cwd2) {
152813
153587
  if (raw === void 0)
152814
153588
  return { ok: true, value: [] };
@@ -155286,7 +156060,7 @@ import * as os18 from "os";
155286
156060
  import * as crypto7 from "crypto";
155287
156061
  import { createRequire } from "module";
155288
156062
  import { fileURLToPath as fileURLToPath2 } from "url";
155289
- import { execSync, execFileSync as execFileSync5 } from "child_process";
156063
+ import { execFile as execFile2, execFileSync as execFileSync5 } from "child_process";
155290
156064
 
155291
156065
  // src/permissions/handler.ts
155292
156066
  import * as readline2 from "readline";
@@ -156268,6 +157042,117 @@ function currentAudit() {
156268
157042
 
156269
157043
  // src/commands/chat.ts
156270
157044
  var import_code_core19 = __toESM(require_dist3(), 1);
157045
+
157046
+ // src/ui/mcpElicit.ts
157047
+ function fieldsOf(schema) {
157048
+ const props = schema?.properties ?? {};
157049
+ const required = new Set(Array.isArray(schema?.required) ? schema.required.map(String) : []);
157050
+ return Object.entries(props).map(([name, p]) => ({
157051
+ name,
157052
+ title: typeof p.title === "string" ? p.title : name,
157053
+ description: typeof p.description === "string" ? p.description : void 0,
157054
+ type: typeof p.type === "string" ? p.type : "string",
157055
+ enumValues: Array.isArray(p.enum) ? p.enum : void 0,
157056
+ required: required.has(name)
157057
+ }));
157058
+ }
157059
+ function coerce(raw, type) {
157060
+ if (type === "number" || type === "integer") {
157061
+ const n = type === "integer" ? parseInt(raw, 10) : Number(raw);
157062
+ return Number.isFinite(n) ? { ok: true, value: n } : { ok: false };
157063
+ }
157064
+ return { ok: true, value: raw };
157065
+ }
157066
+ async function elicitForm(server, req, io) {
157067
+ const fields = fieldsOf(req.requestedSchema);
157068
+ io.print(paint.dim(` ${server} asks: ${truncateCells(req.message, 300)}`));
157069
+ if (!fields.length) {
157070
+ const idx = await io.choose({
157071
+ title: `${server} is asking for your confirmation`,
157072
+ // The 'n'-keyed option is what Esc / Ctrl+C resolve to (choiceBox.denyIndex).
157073
+ options: [{ key: "y", label: "Continue" }, { key: "n", label: "Decline" }, { key: "c", label: "Cancel" }]
157074
+ });
157075
+ if (idx < 0)
157076
+ return { action: "decline" };
157077
+ return idx === 0 ? { action: "accept", content: {} } : { action: idx === 1 ? "decline" : "cancel" };
157078
+ }
157079
+ const content = {};
157080
+ for (const f3 of fields) {
157081
+ const label = f3.title + (f3.required ? "" : " (optional)");
157082
+ if (f3.enumValues?.length) {
157083
+ const options = f3.enumValues.map((v, i2) => ({ key: String(i2 + 1), label: String(v) }));
157084
+ options.push({ key: "n", label: "\u2014 decline \u2014" }, { key: "c", label: "\u2014 cancel \u2014" });
157085
+ const idx = await io.choose({ title: `${server}: ${label}`, detail: f3.description, options });
157086
+ if (idx < 0)
157087
+ return { action: "decline" };
157088
+ if (idx === f3.enumValues.length)
157089
+ return { action: "decline" };
157090
+ if (idx === f3.enumValues.length + 1)
157091
+ return { action: "cancel" };
157092
+ content[f3.name] = f3.enumValues[idx];
157093
+ continue;
157094
+ }
157095
+ if (f3.type === "boolean") {
157096
+ const idx = await io.choose({
157097
+ title: `${server}: ${label}`,
157098
+ detail: f3.description,
157099
+ // Esc means "leave the form", NOT "answer false" — a silent wrong answer is worse
157100
+ // than a dismissal, so 'n' is the decline sentinel here too.
157101
+ options: [
157102
+ { key: "y", label: "Yes" },
157103
+ { key: "f", label: "No" },
157104
+ { key: "n", label: "\u2014 decline \u2014" },
157105
+ { key: "c", label: "\u2014 cancel \u2014" }
157106
+ ]
157107
+ });
157108
+ if (idx < 0 || idx === 2)
157109
+ return { action: "decline" };
157110
+ if (idx === 3)
157111
+ return { action: "cancel" };
157112
+ content[f3.name] = idx === 0;
157113
+ continue;
157114
+ }
157115
+ const raw = (await io.line(` ${server}: ${label}${f3.description ? ` \u2014 ${f3.description}` : ""}: `)).trim();
157116
+ if (!raw) {
157117
+ if (f3.required)
157118
+ return { action: "decline" };
157119
+ continue;
157120
+ }
157121
+ const c = coerce(raw, f3.type);
157122
+ if (!c.ok) {
157123
+ io.print(paint.yellow(` ${server}: "${raw}" is not a ${f3.type} \u2014 declining.`));
157124
+ return { action: "decline" };
157125
+ }
157126
+ content[f3.name] = c.value;
157127
+ }
157128
+ return { action: "accept", content };
157129
+ }
157130
+ async function elicitUrl(server, req, io) {
157131
+ if (!req.url)
157132
+ return { action: "decline" };
157133
+ io.print(paint.dim(` ${server} asks: ${truncateCells(req.message, 300)}`));
157134
+ const idx = await io.choose({
157135
+ title: `${server} needs you to open a page`,
157136
+ detail: truncateCells(req.url, 120),
157137
+ options: [{ key: "o", label: "Open in browser" }, { key: "n", label: "Decline" }, { key: "c", label: "Cancel" }]
157138
+ });
157139
+ if (idx < 0)
157140
+ return { action: "decline" };
157141
+ if (idx === 0) {
157142
+ io.openUrl(req.url);
157143
+ return { action: "accept" };
157144
+ }
157145
+ return { action: idx === 1 ? "decline" : "cancel" };
157146
+ }
157147
+ async function elicit(server, req, io) {
157148
+ try {
157149
+ return req.mode === "url" ? await elicitUrl(server, req, io) : await elicitForm(server, req, io);
157150
+ } catch {
157151
+ return { action: "decline" };
157152
+ }
157153
+ }
157154
+
157155
+ // src/commands/chat.ts
156271
157156
  var import_code_core20 = __toESM(require_dist3(), 1);
156272
157157
 
156273
157158
  // src/debugLog.ts
@@ -172348,12 +173233,16 @@ function resolveModelLabel(alias) {
172348
173233
  const id = normaliseModelId(alias);
172349
173234
  return (0, import_code_core18.liveLabelFor)(id, MODEL_LABELS[id] ?? id);
172350
173235
  }
172351
- function tryExec(cmd, cwd2) {
172352
- try {
172353
- return execSync(cmd, { cwd: cwd2, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim() || void 0;
172354
- } catch {
172355
- return void 0;
172356
- }
173236
+ async function tryExecAsync(file, args, cwd2) {
173237
+ return new Promise((resolve12) => {
173238
+ try {
173239
+ execFile2(file, args, { cwd: cwd2, encoding: "utf-8" }, (err, stdout) => {
173240
+ resolve12(err ? void 0 : String(stdout).trim() || void 0);
173241
+ });
173242
+ } catch {
173243
+ resolve12(void 0);
173244
+ }
173245
+ });
172357
173246
  }
172358
173247
  var BILLING_URL = "https://app.nexrall.com/?settings=billing";
172359
173248
  function hyperlink(label, url) {
@@ -172381,28 +173270,29 @@ function currentUserLabel() {
172381
173270
  return "there";
172382
173271
  }
172383
173272
  }
172384
- function collectEnv(workDir) {
173273
+ async function collectEnv(workDir) {
172385
173274
  return {
172386
173275
  cwd: workDir,
172387
173276
  platform: `${os18.platform()} ${os18.release()}`,
172388
173277
  shell: process.env.SHELL ?? process.env.ComSpec ?? "unknown",
172389
173278
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
172390
- ...collectGitEnv(workDir)
173279
+ ...await collectGitEnv(workDir)
172391
173280
  };
172392
173281
  }
172393
- function collectGitEnv(workDir) {
172394
- return {
172395
- gitBranch: tryExec("git branch --show-current", workDir),
172396
- gitStatus: tryExec("git status --short", workDir),
172397
- gitDiff: tryExec("git diff --stat HEAD", workDir),
172398
- recentCommits: tryExec("git log --oneline -5", workDir)
172399
- };
173282
+ async function collectGitEnv(workDir) {
173283
+ const [gitBranch2, gitStatus, gitDiff, recentCommits] = await Promise.all([
173284
+ tryExecAsync("git", ["branch", "--show-current"], workDir),
173285
+ tryExecAsync("git", ["status", "--short"], workDir),
173286
+ tryExecAsync("git", ["diff", "--stat", "HEAD"], workDir),
173287
+ tryExecAsync("git", ["log", "--oneline", "-5"], workDir)
173288
+ ]);
173289
+ return { gitBranch: gitBranch2, gitStatus, gitDiff, recentCommits };
172400
173290
  }
172401
- function refreshEnvForTurn(env4, workDir) {
173291
+ async function refreshEnvForTurn(env4, workDir) {
172402
173292
  return {
172403
173293
  ...env4,
172404
173294
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
172405
- ...collectGitEnv(workDir)
173295
+ ...await collectGitEnv(workDir)
172406
173296
  };
172407
173297
  }
172408
173298
  var noDiscovery = false;
@@ -172586,6 +173476,10 @@ var Spinner = class {
172586
173476
  verbIdx = 0;
172587
173477
  thinkTokens = 0;
172588
173478
  thinkText = "";
173479
+ // A running hook's `statusMessage` (Claude Code). While set it REPLACES the label —
173480
+ // the whole point is telling the user which hook is producing the wait — and the
173481
+ // normal label returns the moment the hook finishes (listener sends null).
173482
+ hookStatus = "";
172589
173483
  start(text, opts) {
172590
173484
  this.stop();
172591
173485
  this.baseText = text;
@@ -172640,13 +173534,24 @@ var Spinner = class {
172640
173534
  return;
172641
173535
  this.baseText = text;
172642
173536
  }
173537
+ /**
173538
+ * A hook with `statusMessage` started (message) or finished (null). Renders immediately
173539
+ * rather than on the next tick: the whole point of the message is that a long hook is
173540
+ * RUNNING, and waiting up to 100ms to admit that is the kind of silence it exists to fix.
173541
+ * No-op while the spinner is stopped — an idle session keeps its prompt, not a status line.
173542
+ */
173543
+ setHookStatus(message) {
173544
+ this.hookStatus = message ?? "";
173545
+ if (this.interval !== null)
173546
+ this.render();
173547
+ }
172643
173548
  render() {
172644
173549
  const elapsedSec = Math.floor((Date.now() - this.startedAt) / 1e3);
172645
173550
  if (this.verbs && elapsedSec > 0 && elapsedSec % VERB_ROTATE_SEC === 0 && this.lastRotateSec !== elapsedSec) {
172646
173551
  this.lastRotateSec = elapsedSec;
172647
173552
  this.verbIdx++;
172648
173553
  }
172649
- const label = this.verbs ? `${WORKING_VERBS[this.verbIdx % WORKING_VERBS.length]}\u2026` : this.baseText;
173554
+ const label = this.hookStatus || (this.verbs ? `${WORKING_VERBS[this.verbIdx % WORKING_VERBS.length]}\u2026` : this.baseText);
172650
173555
  const status = composeStatusLine({
172651
173556
  frame: this.frames[this.i % this.frames.length],
172652
173557
  text: label,
@@ -172689,12 +173594,14 @@ function nextBackgroundChange() {
172689
173594
  }
172690
173595
  var _mcpManager = null;
172691
173596
  var _askIO = null;
173597
+ var _elicitationHandler = null;
172692
173598
  var _mcpConfig = null;
172693
173599
  var sessionTouched = [];
172694
173600
  async function initMcp(workDir, override, strict) {
172695
173601
  if (noDiscovery && !override) {
172696
173602
  _mcpConfig = { mcpServers: {} };
172697
173603
  _mcpManager = null;
173604
+ wireHookMcpCaller();
172698
173605
  return;
172699
173606
  }
172700
173607
  _mcpConfig = strict && override ? { mcpServers: {} } : import_code_core18.McpManager.loadConfig(workDir);
@@ -172702,6 +173609,7 @@ async function initMcp(workDir, override, strict) {
172702
173609
  Object.assign(_mcpConfig.mcpServers, override.mcpServers);
172703
173610
  if (Object.keys(_mcpConfig.mcpServers).length === 0) {
172704
173611
  _mcpManager = null;
173612
+ wireHookMcpCaller();
172705
173613
  return;
172706
173614
  }
172707
173615
  try {
@@ -172709,8 +173617,48 @@ async function initMcp(workDir, override, strict) {
172709
173617
  } catch {
172710
173618
  }
172711
173619
  const manager = new import_code_core18.McpManager();
173620
+ manager.setElicitationHandler(_elicitationHandler);
172712
173621
  await manager.connectAll(_mcpConfig);
172713
173622
  _mcpManager = manager;
173623
+ wireHookMcpCaller();
173624
+ }
173625
+ function wireHookMcpCaller() {
173626
+ const mgr = _mcpManager;
173627
+ if (!mgr) {
173628
+ (0, import_code_core19.setHookMcpCaller)(null);
173629
+ return;
173630
+ }
173631
+ (0, import_code_core19.setHookMcpCaller)(async (server, tool, input) => {
173632
+ const text = await mgr.callTool(`${server}__${tool}`, input);
173633
+ return text === null ? { error: `MCP server "${server}" has no tool "${tool}" (or the server is not connected)` } : { text };
173634
+ });
173635
+ }
173636
+ async function handleElicitation(server, req, workDir, getSessionId, interactive) {
173637
+ const hookAnswer = await (0, import_code_core19.runElicitationHooks)(workDir, server, {
173638
+ message: req.message,
173639
+ mode: req.mode,
173640
+ url: req.url,
173641
+ elicitation_id: req.elicitationId,
173642
+ requested_schema: req.requestedSchema
173643
+ }, getSessionId());
173644
+ let answer;
173645
+ if (hookAnswer)
173646
+ answer = hookAnswer;
173647
+ else if (!interactive || !_askIO)
173648
+ answer = { action: "decline" };
173649
+ else {
173650
+ answer = await elicit(server, req, {
173651
+ choose: _askIO.choose,
173652
+ line: _askIO.line,
173653
+ print: (t2) => console.log(t2),
173654
+ openUrl: (url) => openInBrowser(url)
173655
+ });
173656
+ }
173657
+ return await (0, import_code_core19.runElicitationResultHooks)(workDir, server, answer, {
173658
+ sessionId: getSessionId(),
173659
+ mode: req.mode,
173660
+ elicitationId: req.elicitationId
173661
+ });
172714
173662
  }
172715
173663
  function formatAgentsList(workDir) {
172716
173664
  const { types: types3, warnings } = (0, import_code_core18.loadAgentTypesWithWarnings)(workDir);
@@ -173086,6 +174034,7 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env4, nexrall
173086
174034
  const turnStart = Date.now();
173087
174035
  userActiveThisTurn = false;
173088
174036
  const spinner = new Spinner();
174037
+ (0, import_code_core19.setHookStatusListener)((m2) => spinner.setHookStatus(m2));
173089
174038
  const mdRender = new MarkdownStreamRenderer();
173090
174039
  const toolStream = new ToolStreamPrinter(PREVIEW_LINES);
173091
174040
  let toolStartTime = 0;
@@ -173112,7 +174061,7 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env4, nexrall
173112
174061
  workDir,
173113
174062
  sessionId: activeSessionIdGetter(),
173114
174063
  model: modelAlias,
173115
- env: refreshEnvForTurn(env4, workDir),
174064
+ env: await refreshEnvForTurn(env4, workDir),
173116
174065
  nexrallMd,
173117
174066
  mode,
173118
174067
  // `/mode plan` previously only hinted to the backend — the footer said "plan
@@ -173465,7 +174414,7 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env4
173465
174414
  workDir,
173466
174415
  sessionId: activeSessionIdGetter(),
173467
174416
  model: modelAlias,
173468
- env: refreshEnvForTurn(env4, workDir),
174417
+ env: await refreshEnvForTurn(env4, workDir),
173469
174418
  nexrallMd,
173470
174419
  mode,
173471
174420
  planMode: mode === "plan",
@@ -173991,7 +174940,7 @@ Resume it with: nex --worktree ${worktreeState.name}
173991
174940
  void (0, import_code_core19.fireObserverHook)(requestedWorkDir, "WorktreeRemove", { name: worktreeState.name, path: worktreeState.worktreePath }, worktreeState.name);
173992
174941
  });
173993
174942
  }
173994
- const env4 = collectEnv(workDir);
174943
+ const env4 = await collectEnv(workDir);
173995
174944
  let briefBase = options.systemPrompt?.trim() ?? readNexrallMd(workDir);
173996
174945
  const agentPrefix = options.selectedAgent?.prompt?.trim();
173997
174946
  const briefParts = [agentPrefix, briefBase, options.appendSystemPrompt?.trim(), addDirNote(options.addDirs ?? []), options.jsonSchema ? schemaInstruction(options.jsonSchema) : ""];
@@ -174005,6 +174954,13 @@ Resume it with: nex --worktree ${worktreeState.name}
174005
174954
  _headlessPolicy.verbose = !!options.verbose;
174006
174955
  _headlessPolicy.includePartial = !!options.includePartialMessages;
174007
174956
  _headlessPolicy.permissionMode = String(options.initialMode ?? "");
174957
+ const streamHookEvents = options.outputFormat === "stream-json";
174958
+ const allHookEvents = !!options.includeHookEvents;
174959
+ (0, import_code_core19.setHookEventListener)(streamHookEvents ? (evt) => {
174960
+ if (!allHookEvents && evt.hookEvent !== "SessionStart" && evt.hookEvent !== "Setup")
174961
+ return;
174962
+ process.stdout.write(JSON.stringify(hookEventPayload({ ...evt, sessionId: activeSessionIdGetter() ?? "", uuid: crypto7.randomUUID() })) + "\n");
174963
+ } : null);
174008
174964
  const saveSession2 = options.noSessionPersistence ? () => {
174009
174965
  } : saveSession;
174010
174966
  const interactive = !headless && !options.prompt && !options.stdinText;
@@ -174127,6 +175083,7 @@ Resume it with: nex --worktree ${worktreeState.name}
174127
175083
  setExcludedPatterns(contextSet.excluded);
174128
175084
  console.log((edit.ok ? paint.green : paint.yellow)(" " + edit.message));
174129
175085
  };
175086
+ _elicitationHandler = (server, req) => handleElicitation(server, req, workDir, () => sessionId, interactive);
174130
175087
  const mcpStarted = (async () => {
174131
175088
  try {
174132
175089
  await initMcp(workDir, options.mcpOverride, options.strictMcp);
@@ -175114,6 +176071,7 @@ ${text}` : "");
175114
176071
  activeGoal = null;
175115
176072
  criteriaLastLine = "";
175116
176073
  cliBackgroundAgents.stopAll({ dropPending: true });
176074
+ (0, import_code_core19.resetHookOnceState)();
175117
176075
  inputBuffer = "";
175118
176076
  if (interactive)
175119
176077
  await getInkTerminal().clearTranscript();
@@ -175320,7 +176278,7 @@ ${summaryText}` }] },
175320
176278
  }
175321
176279
  const readme = tryRead(path29.join(workDir, "README.md"))?.slice(0, 2e3) ?? "";
175322
176280
  const pkgJson = tryRead(path29.join(workDir, "package.json"))?.slice(0, 1e3) ?? "";
175323
- const dirList = tryExec("ls -la", workDir)?.slice(0, 500) ?? "";
176281
+ const dirList = (await tryExecAsync("ls", ["-la"], workDir))?.slice(0, 500) ?? "";
175324
176282
  const initPrompt = `Analyze this project and write a concise nexrall.md (project-specific instructions for an AI coding assistant).
175325
176283
  Include: project overview, tech stack, coding conventions, important files/folders, what to avoid.
175326
176284
  Keep it under 400 words. Use markdown. Be specific and actionable.
@@ -177246,6 +178204,7 @@ ${text}
177246
178204
  rl.resume();
177247
178205
  rl.prompt();
177248
178206
  deliverBackgroundWhenIdle();
178207
+ deliverHookWakeWhenIdle();
177249
178208
  });
177250
178209
  async function runGoalLoop() {
177251
178210
  while (activeGoal) {
@@ -177326,6 +178285,20 @@ ${text}
177326
178285
  bgLastCount = n;
177327
178286
  deliverBackgroundWhenIdle();
177328
178287
  });
178288
+ let hookWakeTimer = null;
178289
+ function deliverHookWakeWhenIdle() {
178290
+ if (hookWakeTimer || agentRunning || !(0, import_code_core19.hasPendingHookWake)())
178291
+ return;
178292
+ hookWakeTimer = setTimeout(() => {
178293
+ hookWakeTimer = null;
178294
+ if (agentRunning || !(0, import_code_core19.hasPendingHookWake)())
178295
+ return;
178296
+ const wakes = (0, import_code_core19.takeHookWakes)();
178297
+ console.log(paint.dim(` \u2699 hook wake \u2014 continuing`));
178298
+ rl.emit("line", (0, import_code_core19.formatHookDeliveries)(wakes));
178299
+ }, 400);
178300
+ }
178301
+ (0, import_code_core19.setHookWakeListener)(deliverHookWakeWhenIdle);
177329
178302
  const loopTimer = setInterval(() => {
177330
178303
  if (loopScheduler.size === 0)
177331
178304
  return;
@@ -178138,7 +179111,7 @@ program2.command("sessions").description("List, export, import, or delete saved
178138
179111
  console.log(paint.dim(" nex sessions --events <id> [--json] to follow its event stream"));
178139
179112
  console.log();
178140
179113
  });
178141
- program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, --turbo", "Use Claude Sonnet 5.5 (fast)").option("-u, --ultra", "Use Claude Fable 5.1 (most powerful)").option("-y, --yolo", "Auto-approve all tool calls (no permission prompts)").option("-d, --dir <path>", "Set working directory (default: current directory)").option("-m, --model <name>", "Explicit model, e.g. claude-opus-5-5 | gpt-5.4 | gpt-4.1").option("-r, --resume [id]", "Resume last session, or a specific session id").option("-c, --continue", "Continue the latest conversation in this folder").option("--plan", "Start in plan mode (read-only; /mode or Shift+Tab can leave it)").option("--readonly", "Read-only for the whole session: plan mode that cannot be switched off").option("--auto", "Start in auto mode (the default \u2014 edits and commands run without asking)").option("--mode <mode>", "Starting permission mode: ask | edit | auto | plan").option("--fullscreen", "Draw the whole session on the terminal's alternate screen: the transcript re-wraps on resize and PgUp/PgDn page through it (no terminal scrollback while it is on; /fullscreen toggles it)").option("--bg", "Run the prompt in the background (headless); returns a job id. See: nex jobs | logs | attach | kill").option("--debug", "Write request timing, retries and errors to a debug log file").option("--no-color", "Disable colored output (also honours NO_COLOR)").option("-w, --worktree [name]", "Isolate this session in its own worktree \u2014 a separate directory (and, in a git repo, its own branch) so it can never overwrite files another session (or you) are touching in the main checkout. Omit a name for an auto-generated one; pass an existing name to resume that same worktree.").option("-e, --effort <level>", "Thinking effort \u2014 real levels depend on the model (e.g. DeepSeek: low|high|max, Qwen: low|high); run `nex --model <id>` then `/effort` with no argument to see a model's scale").option("--no-banner", "Skip the ASCII banner (useful in scripts/pipes)").option("--print", "One-shot, non-interactive run that prints the answer and exits (same as --output-format text). Claude Code's `-p` is spelled --print here because `-p` already means --pro").option("--output-format <fmt>", "One-shot output format: text | json | stream-json (implies auto-approve)").option("--audit [path]", "Log every tool call (and refusal) to a JSONL trail (default: .nexrall/audit/<session>.jsonl)").option("--permission-mode <mode>", "Permission mode: ask | edit | auto | plan (also Claude Code names: default | acceptEdits | dontAsk | bypassPermissions). With --output-format, anything that would prompt is DENIED instead of auto-approved").option("--allowedTools, --allowed-tools <tools...>", 'Tools/rules to allow without asking, e.g. "Bash(git *)" Edit (comma or space separated; put the prompt BEFORE this flag)').option("--disallowedTools, --disallowed-tools <tools...>", 'Tools/rules to deny, e.g. "Bash(rm *)" (deny always wins)').option("--append-system-prompt <text>", "Extra instructions appended to the project brief for this run").option("--max-budget-usd <amount>", "Stop a one-shot run once it has spent this many dollars (needs --output-format)").option("--mcp-config <configs...>", 'Load MCP servers from JSON files or inline JSON ({"mcpServers":{\u2026}}) for this run').option("--strict-mcp-config", "With --mcp-config: use ONLY those servers, ignoring ~/.nexrall/mcp.json and the project one").option("--add-dir <dirs...>", "Extra directories the agent may read and edit besides the project folder").option("--input-format <format>", "text (default) | stream-json \u2014 read the prompt as Claude-Code-style NDJSON user messages on stdin").option("--json-schema <schema>", 'JSON Schema (inline or file) the final answer must satisfy; needs --output-format. Result gains "structured_output"; exit 3 if it does not validate').option("--no-session-persistence", "Do not save this run to disk (needs --output-format; it cannot be resumed)").option("--max-turns <n>", "Stop a headless run after N agent rounds (needs --output-format; exit 4 when it hits the limit)").option("--permission-prompt-tool <t>", "MCP tool (mcp__server__tool) that answers permission prompts in non-interactive runs \u2014 instead of nex denying them").option("--append-system-prompt-file <path>", "Like --append-system-prompt, read from a file").option("--system-prompt <text>", "REPLACE the project brief (nexrall.md + memory + global instructions) with this text for this run").option("--system-prompt-file <path>", "Like --system-prompt, read from a file").option("--verbose", "Print progress detail (tool calls, timings, usage) to stderr while a one-shot run works").option("--include-partial-messages", "Stream model deltas as they arrive (needs --output-format stream-json)").option("--replay-user-messages", "Echo each stdin message back on stdout as it is consumed (needs --input-format stream-json --output-format stream-json)").option("--agents <json-or-file>", 'Define extra sub-agents for this run: a JSON object { "name": { "description", "prompt", "tools", "model" } } or @file.json').option("--agent <name>", "Run this session AS that agent (its prompt, tools and model), from .nexrall/agents, a plugin, --agents or a builtin").option("--bare", "Start without discovery: no nexrall.md/memory, skills, plugins, hooks or MCP config files").option("--safe-mode", "Same as --bare (Claude Code's troubleshooting name)").option("--tools <list>", 'Exactly these built-in tools ("Bash,Read,Edit", "default", or "" for none); the rest are removed, not just unapproved').option("--session-id <id>", "Use this id for a NEW conversation (letters, digits, - _), so a script can find it again").option("-n, --name <name>", "Name the conversation; `nex -r <name>` resumes it by that name").option("--fork-session", "With --resume/--continue: branch into a new session instead of continuing the original").option("--fallback-model <name>", "If the main model is overloaded or unreachable, retry the turn once on this model (needs --output-format)").option("--settings <file|json>", "Settings (permissions, hooks\u2026) for this run only: a JSON file or an inline JSON object; merged over the user/project settings").option("--debug-file <path>", "Write the --debug diagnostics log to this file (implies --debug)").argument("[prompt...]", "One-shot prompt \u2014 if omitted, starts interactive mode").action(async (promptParts, options) => {
179114
+ program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, --turbo", "Use Claude Sonnet 5.5 (fast)").option("-u, --ultra", "Use Claude Fable 5.1 (most powerful)").option("-y, --yolo", "Auto-approve all tool calls (no permission prompts)").option("-d, --dir <path>", "Set working directory (default: current directory)").option("-m, --model <name>", "Explicit model, e.g. claude-opus-5-5 | gpt-5.4 | gpt-4.1").option("-r, --resume [id]", "Resume last session, or a specific session id").option("-c, --continue", "Continue the latest conversation in this folder").option("--plan", "Start in plan mode (read-only; /mode or Shift+Tab can leave it)").option("--readonly", "Read-only for the whole session: plan mode that cannot be switched off").option("--auto", "Start in auto mode (the default \u2014 edits and commands run without asking)").option("--mode <mode>", "Starting permission mode: ask | edit | auto | plan").option("--fullscreen", "Draw the whole session on the terminal's alternate screen: the transcript re-wraps on resize and PgUp/PgDn page through it (no terminal scrollback while it is on; /fullscreen toggles it)").option("--bg", "Run the prompt in the background (headless); returns a job id. See: nex jobs | logs | attach | kill").option("--debug", "Write request timing, retries and errors to a debug log file").option("--no-color", "Disable colored output (also honours NO_COLOR)").option("-w, --worktree [name]", "Isolate this session in its own worktree \u2014 a separate directory (and, in a git repo, its own branch) so it can never overwrite files another session (or you) are touching in the main checkout. Omit a name for an auto-generated one; pass an existing name to resume that same worktree.").option("-e, --effort <level>", "Thinking effort \u2014 real levels depend on the model (e.g. DeepSeek: low|high|max, Qwen: low|high); run `nex --model <id>` then `/effort` with no argument to see a model's scale").option("--no-banner", "Skip the ASCII banner (useful in scripts/pipes)").option("--print", "One-shot, non-interactive run that prints the answer and exits (same as --output-format text). Claude Code's `-p` is spelled --print here because `-p` already means --pro").option("--output-format <fmt>", "One-shot output format: text | json | stream-json (implies auto-approve)").option("--audit [path]", "Log every tool call (and refusal) to a JSONL trail (default: .nexrall/audit/<session>.jsonl)").option("--permission-mode <mode>", "Permission mode: ask | edit | auto | plan (also Claude Code names: default | acceptEdits | dontAsk | bypassPermissions). With --output-format, anything that would prompt is DENIED instead of auto-approved").option("--allowedTools, --allowed-tools <tools...>", 'Tools/rules to allow without asking, e.g. "Bash(git *)" Edit (comma or space separated; put the prompt BEFORE this flag)').option("--disallowedTools, --disallowed-tools <tools...>", 'Tools/rules to deny, e.g. "Bash(rm *)" (deny always wins)').option("--append-system-prompt <text>", "Extra instructions appended to the project brief for this run").option("--max-budget-usd <amount>", "Stop a one-shot run once it has spent this many dollars (needs --output-format)").option("--mcp-config <configs...>", 'Load MCP servers from JSON files or inline JSON ({"mcpServers":{\u2026}}) for this run').option("--strict-mcp-config", "With --mcp-config: use ONLY those servers, ignoring ~/.nexrall/mcp.json and the project one").option("--add-dir <dirs...>", "Extra directories the agent may read and edit besides the project folder").option("--input-format <format>", "text (default) | stream-json \u2014 read the prompt as Claude-Code-style NDJSON user messages on stdin").option("--json-schema <schema>", 'JSON Schema (inline or file) the final answer must satisfy; needs --output-format. Result gains "structured_output"; exit 3 if it does not validate').option("--no-session-persistence", "Do not save this run to disk (needs --output-format; it cannot be resumed)").option("--max-turns <n>", "Stop a headless run after N agent rounds (needs --output-format; exit 4 when it hits the limit)").option("--permission-prompt-tool <t>", "MCP tool (mcp__server__tool) that answers permission prompts in non-interactive runs \u2014 instead of nex denying them").option("--append-system-prompt-file <path>", "Like --append-system-prompt, read from a file").option("--system-prompt <text>", "REPLACE the project brief (nexrall.md + memory + global instructions) with this text for this run").option("--system-prompt-file <path>", "Like --system-prompt, read from a file").option("--verbose", "Print progress detail (tool calls, timings, usage) to stderr while a one-shot run works").option("--include-partial-messages", "Stream model deltas as they arrive (needs --output-format stream-json)").option("--include-hook-events", "Include hook lifecycle events in the stream-json output (system: hook_started / hook_progress / hook_response)").option("--replay-user-messages", "Echo each stdin message back on stdout as it is consumed (needs --input-format stream-json --output-format stream-json)").option("--agents <json-or-file>", 'Define extra sub-agents for this run: a JSON object { "name": { "description", "prompt", "tools", "model" } } or @file.json').option("--agent <name>", "Run this session AS that agent (its prompt, tools and model), from .nexrall/agents, a plugin, --agents or a builtin").option("--bare", "Start without discovery: no nexrall.md/memory, skills, plugins, hooks or MCP config files").option("--safe-mode", "Same as --bare (Claude Code's troubleshooting name)").option("--tools <list>", 'Exactly these built-in tools ("Bash,Read,Edit", "default", or "" for none); the rest are removed, not just unapproved').option("--session-id <id>", "Use this id for a NEW conversation (letters, digits, - _), so a script can find it again").option("-n, --name <name>", "Name the conversation; `nex -r <name>` resumes it by that name").option("--fork-session", "With --resume/--continue: branch into a new session instead of continuing the original").option("--fallback-model <name>", "If the main model is overloaded or unreachable, retry the turn once on this model (needs --output-format)").option("--settings <file|json>", "Settings (permissions, hooks\u2026) for this run only: a JSON file or an inline JSON object; merged over the user/project settings").option("--debug-file <path>", "Write the --debug diagnostics log to this file (implies --debug)").argument("[prompt...]", "One-shot prompt \u2014 if omitted, starts interactive mode").action(async (promptParts, options) => {
178142
179115
  if (!(0, import_code_core22.isAuthenticated)()) {
178143
179116
  console.error("Not logged in. Run: nex auth");
178144
179117
  process.exit(1);
@@ -178410,6 +179383,11 @@ program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, -
178410
179383
  console.error(partialErr);
178411
179384
  process.exit(1);
178412
179385
  }
179386
+ const hookEventsErr = checkHookEvents({ includeHookEvents: !!options.includeHookEvents, outputFormat });
179387
+ if (hookEventsErr) {
179388
+ console.error(hookEventsErr);
179389
+ process.exit(1);
179390
+ }
178413
179391
  const replayErr = checkReplayUserMessages({ replay: !!options.replayUserMessages, inputFormat: options.inputFormat, outputFormat });
178414
179392
  if (replayErr) {
178415
179393
  console.error(replayErr);
@@ -178434,7 +179412,7 @@ program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, -
178434
179412
  }
178435
179413
  effort = lvl;
178436
179414
  }
178437
- await startChatSession({ model, workDir, prompt: prompt2, resume, continueLast: !!options.continue, initialMode: modeRes.mode, lockMode: modeRes.locked, debug: !!options.debug || debugFile !== void 0, debugFile, fallbackModel, maxTurns, onlyTools, newSessionId, sessionName, forkSession: !!options.forkSession, stdinText, stdinFollowUps, showBanner, outputFormat, effort, audit: options.audit, worktree, fullscreen: !!options.fullscreen, strictPermissions: options.permissionMode !== void 0, budgetUsd, appendSystemPrompt, mcpOverride, strictMcp: !!options.strictMcpConfig, systemPrompt: sysPrompt.value, verbose: !!options.verbose, includePartialMessages: !!options.includePartialMessages, agents: agentsParsed.value, selectedAgent: selectedAgent ? { name: selectedAgent.name, prompt: selectedAgent.prompt, tools: selectedAgent.tools, model: selectedAgent.model } : void 0, replayUserMessages: !!options.replayUserMessages, inputFormatWasStreamJson: options.inputFormat !== void 0 && String(options.inputFormat).toLowerCase() === "stream-json", bare: !!(options.bare || options.safeMode), noSessionPersistence: options.sessionPersistence === false, addDirs: addDirs.value, jsonSchema, permissionPromptTool });
179415
+ await startChatSession({ model, workDir, prompt: prompt2, resume, continueLast: !!options.continue, initialMode: modeRes.mode, lockMode: modeRes.locked, debug: !!options.debug || debugFile !== void 0, debugFile, fallbackModel, maxTurns, onlyTools, newSessionId, sessionName, forkSession: !!options.forkSession, stdinText, stdinFollowUps, showBanner, outputFormat, effort, audit: options.audit, worktree, fullscreen: !!options.fullscreen, strictPermissions: options.permissionMode !== void 0, budgetUsd, appendSystemPrompt, mcpOverride, strictMcp: !!options.strictMcpConfig, systemPrompt: sysPrompt.value, verbose: !!options.verbose, includePartialMessages: !!options.includePartialMessages, includeHookEvents: !!options.includeHookEvents, agents: agentsParsed.value, selectedAgent: selectedAgent ? { name: selectedAgent.name, prompt: selectedAgent.prompt, tools: selectedAgent.tools, model: selectedAgent.model } : void 0, replayUserMessages: !!options.replayUserMessages, inputFormatWasStreamJson: options.inputFormat !== void 0 && String(options.inputFormat).toLowerCase() === "stream-json", bare: !!(options.bare || options.safeMode), noSessionPersistence: options.sessionPersistence === false, addDirs: addDirs.value, jsonSchema, permissionPromptTool });
178438
179416
  });
178439
179417
  program2.parse();
178440
179418
  /*! Bundled license information: