nexrall-code 0.5.178 → 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 +593 -34
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -106714,6 +106714,8 @@ var require_hooks = __commonJS({
106714
106714
  exports2.formatHookDeliveries = formatHookDeliveries2;
106715
106715
  exports2.resetHookOnceState = resetHookOnceState2;
106716
106716
  exports2.setHookMcpCaller = setHookMcpCaller2;
106717
+ exports2.setHookEventListener = setHookEventListener2;
106718
+ exports2.hasHookEventListener = hasHookEventListener;
106717
106719
  exports2.parsePromptHookReply = parsePromptHookReply;
106718
106720
  exports2.hookRunOptsFor = hookRunOptsFor;
106719
106721
  exports2.classifyStopFailure = classifyStopFailure;
@@ -106722,6 +106724,8 @@ var require_hooks = __commonJS({
106722
106724
  exports2.runSimpleHooks = runSimpleHooks;
106723
106725
  exports2.runLifecycleHooks = runLifecycleHooks;
106724
106726
  exports2.runPermissionRequestHooks = runPermissionRequestHooks3;
106727
+ exports2.runElicitationHooks = runElicitationHooks2;
106728
+ exports2.runElicitationResultHooks = runElicitationResultHooks2;
106725
106729
  exports2.fireObserverHook = fireObserverHook3;
106726
106730
  exports2.fireNotificationHooks = fireNotificationHooks2;
106727
106731
  exports2.fireManualPostCompactHook = fireManualPostCompactHook2;
@@ -106806,7 +106810,9 @@ var require_hooks = __commonJS({
106806
106810
  "Setup",
106807
106811
  "PostToolBatch",
106808
106812
  "TaskCreated",
106809
- "TaskCompleted"
106813
+ "TaskCompleted",
106814
+ "Elicitation",
106815
+ "ElicitationResult"
106810
106816
  ];
106811
106817
  function toHookEntries(list) {
106812
106818
  return list.map((it) => {
@@ -106877,6 +106883,95 @@ ${d.text}
106877
106883
  function setHookMcpCaller2(fn) {
106878
106884
  _hookMcpCaller = fn;
106879
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
+ }
106880
106975
  function isBackgroundHook(hook) {
106881
106976
  return hook.type === "command" && (hook.async === true || hook.asyncRewake === true);
106882
106977
  }
@@ -106909,7 +107004,7 @@ ${d.text}
106909
107004
  if (context)
106910
107005
  _hookDeliveries.push({ kind: "context", text: context, source: opts.eventLabel });
106911
107006
  };
106912
- void spawnHook(String(hook.command ?? ""), { cwd: opts.cwd, timeout: opts.timeoutMs, input: payload, env: opts.env }).then(done, () => done({ status: null, stdout: "", stderr: "" }));
107007
+ void runHookHandler(hook, payload, { cwd: opts.cwd, timeoutMs: opts.timeoutMs, env: opts.env, event: opts.event }).then(done, () => done({ status: null, stdout: "", stderr: "" }));
106913
107008
  }
106914
107009
  function directiveContext(out) {
106915
107010
  if (!out.startsWith("{"))
@@ -106955,12 +107050,16 @@ ${d.text}
106955
107050
  }, opts.timeout);
106956
107051
  const cap = 16 * 1024 * 1024;
106957
107052
  child.stdout?.on("data", (d) => {
107053
+ const s2 = d.toString("utf-8");
106958
107054
  if (stdout.length < cap)
106959
- stdout += d.toString("utf-8");
107055
+ stdout += s2;
107056
+ opts.onData?.({ stdout: s2 });
106960
107057
  });
106961
107058
  child.stderr?.on("data", (d) => {
107059
+ const s2 = d.toString("utf-8");
106962
107060
  if (stderr.length < cap)
106963
- stderr += d.toString("utf-8");
107061
+ stderr += s2;
107062
+ opts.onData?.({ stderr: s2 });
106964
107063
  });
106965
107064
  child.on("error", () => done(null));
106966
107065
  child.on("close", (code) => done(code));
@@ -107068,14 +107167,16 @@ ${d.text}
107068
107167
  return out;
107069
107168
  }
107070
107169
  async function runHookHandler(hook, payload, opts) {
107071
- if (hook.type === "http")
107072
- return httpHook(hook, payload, opts.timeoutMs);
107073
- if (hook.type === "mcp_tool")
107074
- return mcpToolHook(hook, payload);
107075
- if (hook.type === "prompt" || hook.type === "agent") {
107076
- return promptHook(hook, payload, { ...opts, envCtx: opts.hookEnv });
107077
- }
107078
- return spawnHook(String(hook.command ?? ""), { cwd: opts.cwd, timeout: opts.timeoutMs, input: payload, env: opts.env });
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
+ });
107079
107180
  }
107080
107181
  var HOOK_AGENT_TOOLS = [
107081
107182
  {
@@ -107318,6 +107419,7 @@ ${out.output}` : `Error: ${out.error}` : out.output ?? "",
107318
107419
  cwd: workDir,
107319
107420
  env: hookEnv,
107320
107421
  eventLabel,
107422
+ event: phase,
107321
107423
  timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 6e4) : null,
107322
107424
  key: gate.key
107323
107425
  });
@@ -107327,6 +107429,7 @@ ${out.output}` : `Error: ${out.error}` : out.output ?? "",
107327
107429
  cwd: workDir,
107328
107430
  timeoutMs: hookTimeoutMs(hook, 6e4),
107329
107431
  env: hookEnv,
107432
+ event: phase,
107330
107433
  ...hookOpts
107331
107434
  });
107332
107435
  const out = (r2.stdout ?? "").toString().trim();
@@ -107367,10 +107470,10 @@ ${out.output}` : `Error: ${out.error}` : out.output ?? "",
107367
107470
  if (gate.skip)
107368
107471
  continue;
107369
107472
  if (gate.background) {
107370
- startBackgroundHook(hook, "", { cwd: workDir, env: env4, eventLabel: event, timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 1e4) : null, key: gate.key });
107473
+ startBackgroundHook(hook, "", { cwd: workDir, env: env4, eventLabel: event, event, timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 1e4) : null, key: gate.key });
107371
107474
  continue;
107372
107475
  }
107373
- const r2 = await runHookHandler(hook, "", { cwd: workDir, timeoutMs: hookTimeoutMs(hook, 1e4), env: env4, ...hookOpts });
107476
+ const r2 = await runHookHandler(hook, "", { cwd: workDir, timeoutMs: hookTimeoutMs(hook, 1e4), env: env4, event, ...hookOpts });
107374
107477
  if (gate.statusId !== void 0)
107375
107478
  hookStatusEnd(gate.statusId);
107376
107479
  if (r2.status === 0 && gate.key)
@@ -107421,13 +107524,14 @@ ${out.output}` : `Error: ${out.error}` : out.output ?? "",
107421
107524
  cwd: workDir,
107422
107525
  env: { ...process.env, NEXRALL_HOOK_PHASE: event },
107423
107526
  eventLabel: event,
107527
+ event,
107424
107528
  timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 6e4) : null,
107425
107529
  key: gate.key
107426
107530
  });
107427
107531
  continue;
107428
107532
  }
107429
107533
  const timeout = hookTimeoutMs(hook, hook.type === "agent" ? 6e4 : hook.type === "prompt" ? 3e4 : 3e4);
107430
- 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 });
107431
107535
  const out = (r2.stdout ?? "").toString().trim();
107432
107536
  if (out.startsWith("{")) {
107433
107537
  try {
@@ -107481,6 +107585,106 @@ ${out.output}` : `Error: ${out.error}` : out.output ?? "",
107481
107585
  }
107482
107586
  return { answer: null };
107483
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
+ }
107484
107688
  async function fireObserverHook3(workDir, event, payload, matchValue) {
107485
107689
  try {
107486
107690
  const items = loadHooks2(workDir)[event];
@@ -109492,8 +109696,8 @@ var require_loop = __commonJS({
109492
109696
  "../core/dist/agent/loop.js"(exports2) {
109493
109697
  "use strict";
109494
109698
  Object.defineProperty(exports2, "__esModule", { value: true });
109495
- 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.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;
109496
- 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 = 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;
109497
109701
  exports2.dispatchSubAgent = dispatchSubAgent;
109498
109702
  exports2.dispatchBackgroundSubAgent = dispatchBackgroundSubAgent;
109499
109703
  exports2.runAgentLoop = runAgentLoop2;
@@ -109576,6 +109780,18 @@ var require_loop = __commonJS({
109576
109780
  Object.defineProperty(exports2, "resetHookOnceState", { enumerable: true, get: function() {
109577
109781
  return hooks_2.resetHookOnceState;
109578
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
+ } });
109579
109795
  var compaction_1 = require_compaction();
109580
109796
  var compaction_2 = require_compaction();
109581
109797
  Object.defineProperty(exports2, "contextWindowFor", { enumerable: true, get: function() {
@@ -110893,9 +111109,12 @@ var require_client2 = __commonJS({
110893
111109
  "../core/dist/mcp/client.js"(exports2) {
110894
111110
  "use strict";
110895
111111
  Object.defineProperty(exports2, "__esModule", { value: true });
110896
- exports2.McpClient = void 0;
111112
+ exports2.McpClient = exports2.MCP_ELICITATION_CAPABILITY = void 0;
110897
111113
  exports2.flattenResourceContents = flattenResourceContents;
110898
111114
  exports2.flattenPromptMessages = flattenPromptMessages;
111115
+ exports2.elicitationCapability = elicitationCapability;
111116
+ exports2.normalizeElicitationRequest = normalizeElicitationRequest;
111117
+ exports2.serverRequestReply = serverRequestReply;
110899
111118
  var child_process_1 = __require("child_process");
110900
111119
  function flattenResourceContents(result, maxChars = 1e5) {
110901
111120
  const contents = result?.contents ?? [];
@@ -110935,6 +111154,39 @@ var require_client2 = __commonJS({
110935
111154
  return text.length > maxChars ? text.slice(0, maxChars) + `
110936
111155
  \u2026 [truncated, ${text.length - maxChars} more characters]` : text;
110937
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
+ }
110938
111190
  var McpClient = class {
110939
111191
  constructor(name, config) {
110940
111192
  this.config = config;
@@ -110943,11 +111195,15 @@ var require_client2 = __commonJS({
110943
111195
  this._pending = /* @__PURE__ */ new Map();
110944
111196
  this._buf = "";
110945
111197
  this._tools = [];
111198
+ this._elicitationHandler = null;
110946
111199
  this.name = name;
110947
111200
  }
110948
111201
  get tools() {
110949
111202
  return this._tools;
110950
111203
  }
111204
+ setElicitationHandler(handler) {
111205
+ this._elicitationHandler = handler;
111206
+ }
110951
111207
  async connect() {
110952
111208
  if (!this.config.command)
110953
111209
  throw new Error(`MCP server "${this.name}" has no command (stdio transport requires one).`);
@@ -110975,7 +111231,7 @@ var require_client2 = __commonJS({
110975
111231
  });
110976
111232
  await this._request("initialize", {
110977
111233
  protocolVersion: "2024-11-05",
110978
- capabilities: {},
111234
+ capabilities: elicitationCapability(this.config.bareElicitationCapability),
110979
111235
  clientInfo: { name: "nexrall-code", version: "1.0" }
110980
111236
  });
110981
111237
  this._notify("notifications/initialized");
@@ -111056,6 +111312,10 @@ var require_client2 = __commonJS({
111056
111312
  } catch {
111057
111313
  return;
111058
111314
  }
111315
+ if (typeof msg.method === "string" && msg.id != null) {
111316
+ void this._handleServerRequest(msg.id, msg.method, msg.params);
111317
+ return;
111318
+ }
111059
111319
  if (msg.id == null)
111060
111320
  return;
111061
111321
  const pending = this._pending.get(msg.id);
@@ -111068,6 +111328,11 @@ var require_client2 = __commonJS({
111068
111328
  pending.resolve(msg.result);
111069
111329
  }
111070
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
+ }
111071
111336
  _rejectAll(err) {
111072
111337
  for (const { reject } of this._pending.values())
111073
111338
  reject(err);
@@ -111106,17 +111371,21 @@ var require_httpClient = __commonJS({
111106
111371
  this._nextId = 1;
111107
111372
  this._tools = [];
111108
111373
  this._sessionId = null;
111374
+ this._elicitationHandler = null;
111109
111375
  this.name = name;
111110
111376
  }
111111
111377
  get tools() {
111112
111378
  return this._tools;
111113
111379
  }
111380
+ setElicitationHandler(handler) {
111381
+ this._elicitationHandler = handler;
111382
+ }
111114
111383
  async connect() {
111115
111384
  if (!this.config.url)
111116
111385
  throw new Error(`MCP server "${this.name}" has no url (http/sse transport requires one).`);
111117
111386
  await this._rpc("initialize", {
111118
111387
  protocolVersion: "2024-11-05",
111119
- capabilities: {},
111388
+ capabilities: (0, client_1.elicitationCapability)(this.config.bareElicitationCapability),
111120
111389
  clientInfo: { name: "nexrall-code", version: "1.0" }
111121
111390
  });
111122
111391
  await this._notify("notifications/initialized");
@@ -111211,29 +111480,106 @@ var require_httpClient = __commonJS({
111211
111480
  throw new Error(`MCP HTTP ${resp.status} ${resp.statusText}${body ? `: ${body.slice(0, 200)}` : ""}`);
111212
111481
  }
111213
111482
  const contentType = resp.headers.get("content-type") ?? "";
111483
+ if (contentType.includes("text/event-stream"))
111484
+ return await this._consumeSse(resp, id);
111214
111485
  const text = await resp.text();
111215
111486
  if (!text.trim())
111216
111487
  return null;
111217
- 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;
111218
111509
  if (msg.error)
111219
- throw new Error(`MCP error ${msg.error.code}: ${msg.error.message}`);
111220
- 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
+ }
111221
111515
  };
111222
- if (contentType.includes("text/event-stream")) {
111223
- for (const frame of text.split(/\r?\n\r?\n/)) {
111224
- 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 = () => {
111225
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)
111226
111563
  continue;
111227
111564
  try {
111228
- const msg = JSON.parse(dataLines.join("\n"));
111229
- if (msg.id === id)
111230
- return consume(msg);
111565
+ handle2(JSON.parse(data));
111231
111566
  } catch {
111232
111567
  }
111233
111568
  }
111234
- throw new Error("No matching JSON-RPC response found in SSE stream");
111235
111569
  }
111236
- 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
+ });
111237
111583
  }
111238
111584
  };
111239
111585
  exports2.McpHttpClient = McpHttpClient;
@@ -111265,18 +111611,22 @@ var require_sseClient = __commonJS({
111265
111611
  this._closed = false;
111266
111612
  this._pending = /* @__PURE__ */ new Map();
111267
111613
  this._endpointWaiter = null;
111614
+ this._elicitationHandler = null;
111268
111615
  this.name = name;
111269
111616
  }
111270
111617
  get tools() {
111271
111618
  return this._tools;
111272
111619
  }
111620
+ setElicitationHandler(handler) {
111621
+ this._elicitationHandler = handler;
111622
+ }
111273
111623
  async connect() {
111274
111624
  if (!this.config.url)
111275
111625
  throw new Error(`MCP server "${this.name}" has no url (sse transport requires one).`);
111276
111626
  await this._openStream();
111277
111627
  await this._rpc("initialize", {
111278
111628
  protocolVersion: "2024-11-05",
111279
- capabilities: {},
111629
+ capabilities: (0, client_1.elicitationCapability)(this.config.bareElicitationCapability),
111280
111630
  clientInfo: { name: "nexrall-code", version: "1.0" }
111281
111631
  });
111282
111632
  await this._notify("notifications/initialized");
@@ -111429,6 +111779,17 @@ var require_sseClient = __commonJS({
111429
111779
  } catch {
111430
111780
  return;
111431
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
+ }
111432
111793
  if (typeof msg.id === "number" && this._pending.has(msg.id)) {
111433
111794
  const { resolve: resolve12, reject, timer } = this._pending.get(msg.id);
111434
111795
  clearTimeout(timer);
@@ -111437,6 +111798,7 @@ var require_sseClient = __commonJS({
111437
111798
  reject(new Error(`MCP error ${msg.error.code}: ${msg.error.message}`));
111438
111799
  else
111439
111800
  resolve12(msg.result ?? null);
111801
+ return;
111440
111802
  }
111441
111803
  }
111442
111804
  _rpc(method, params) {
@@ -111658,6 +112020,17 @@ var require_manager = __commonJS({
111658
112020
  this._needsAuth = /* @__PURE__ */ new Map();
111659
112021
  this._disabled = /* @__PURE__ */ new Set();
111660
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);
111661
112034
  }
111662
112035
  /** Pick the transport for a server config: stdio (command), Streamable HTTP, or legacy SSE. */
111663
112036
  static makeClient(name, cfg) {
@@ -111703,6 +112076,7 @@ var require_manager = __commonJS({
111703
112076
  return;
111704
112077
  }
111705
112078
  const client = _McpManager.makeClient(name, serverCfg);
112079
+ client.setElicitationHandler(this._elicitationHandler);
111706
112080
  try {
111707
112081
  await client.connect();
111708
112082
  this._clients.set(name, client);
@@ -111962,6 +112336,7 @@ var require_manager = __commonJS({
111962
112336
  return `no server named "${name}" is configured`;
111963
112337
  this._dropClient(name);
111964
112338
  const client = _McpManager.makeClient(name, cfg);
112339
+ client.setElicitationHandler(this._elicitationHandler);
111965
112340
  try {
111966
112341
  await client.connect();
111967
112342
  } catch (err) {
@@ -153147,6 +153522,14 @@ function checkPartialMessages(f3) {
153147
153522
  }
153148
153523
  return null;
153149
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
+ }
153150
153533
  function checkReplayUserMessages(f3) {
153151
153534
  if (!f3.replay)
153152
153535
  return null;
@@ -153177,6 +153560,29 @@ function initEventPayload(i2) {
153177
153560
  function userReplayPayload(text) {
153178
153561
  return { type: "user", message: { role: "user", content: [{ type: "text", text }] } };
153179
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
+ }
153180
153586
  function parseAgentsArg(raw, cwd2) {
153181
153587
  if (raw === void 0)
153182
153588
  return { ok: true, value: [] };
@@ -156636,6 +157042,117 @@ function currentAudit() {
156636
157042
 
156637
157043
  // src/commands/chat.ts
156638
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
156639
157156
  var import_code_core20 = __toESM(require_dist3(), 1);
156640
157157
 
156641
157158
  // src/debugLog.ts
@@ -173077,6 +173594,7 @@ function nextBackgroundChange() {
173077
173594
  }
173078
173595
  var _mcpManager = null;
173079
173596
  var _askIO = null;
173597
+ var _elicitationHandler = null;
173080
173598
  var _mcpConfig = null;
173081
173599
  var sessionTouched = [];
173082
173600
  async function initMcp(workDir, override, strict) {
@@ -173099,6 +173617,7 @@ async function initMcp(workDir, override, strict) {
173099
173617
  } catch {
173100
173618
  }
173101
173619
  const manager = new import_code_core18.McpManager();
173620
+ manager.setElicitationHandler(_elicitationHandler);
173102
173621
  await manager.connectAll(_mcpConfig);
173103
173622
  _mcpManager = manager;
173104
173623
  wireHookMcpCaller();
@@ -173114,6 +173633,33 @@ function wireHookMcpCaller() {
173114
173633
  return text === null ? { error: `MCP server "${server}" has no tool "${tool}" (or the server is not connected)` } : { text };
173115
173634
  });
173116
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
+ });
173662
+ }
173117
173663
  function formatAgentsList(workDir) {
173118
173664
  const { types: types3, warnings } = (0, import_code_core18.loadAgentTypesWithWarnings)(workDir);
173119
173665
  if (!types3.length)
@@ -174408,6 +174954,13 @@ Resume it with: nex --worktree ${worktreeState.name}
174408
174954
  _headlessPolicy.verbose = !!options.verbose;
174409
174955
  _headlessPolicy.includePartial = !!options.includePartialMessages;
174410
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);
174411
174964
  const saveSession2 = options.noSessionPersistence ? () => {
174412
174965
  } : saveSession;
174413
174966
  const interactive = !headless && !options.prompt && !options.stdinText;
@@ -174530,6 +175083,7 @@ Resume it with: nex --worktree ${worktreeState.name}
174530
175083
  setExcludedPatterns(contextSet.excluded);
174531
175084
  console.log((edit.ok ? paint.green : paint.yellow)(" " + edit.message));
174532
175085
  };
175086
+ _elicitationHandler = (server, req) => handleElicitation(server, req, workDir, () => sessionId, interactive);
174533
175087
  const mcpStarted = (async () => {
174534
175088
  try {
174535
175089
  await initMcp(workDir, options.mcpOverride, options.strictMcp);
@@ -178557,7 +179111,7 @@ program2.command("sessions").description("List, export, import, or delete saved
178557
179111
  console.log(paint.dim(" nex sessions --events <id> [--json] to follow its event stream"));
178558
179112
  console.log();
178559
179113
  });
178560
- 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) => {
178561
179115
  if (!(0, import_code_core22.isAuthenticated)()) {
178562
179116
  console.error("Not logged in. Run: nex auth");
178563
179117
  process.exit(1);
@@ -178829,6 +179383,11 @@ program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, -
178829
179383
  console.error(partialErr);
178830
179384
  process.exit(1);
178831
179385
  }
179386
+ const hookEventsErr = checkHookEvents({ includeHookEvents: !!options.includeHookEvents, outputFormat });
179387
+ if (hookEventsErr) {
179388
+ console.error(hookEventsErr);
179389
+ process.exit(1);
179390
+ }
178832
179391
  const replayErr = checkReplayUserMessages({ replay: !!options.replayUserMessages, inputFormat: options.inputFormat, outputFormat });
178833
179392
  if (replayErr) {
178834
179393
  console.error(replayErr);
@@ -178853,7 +179412,7 @@ program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, -
178853
179412
  }
178854
179413
  effort = lvl;
178855
179414
  }
178856
- 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 });
178857
179416
  });
178858
179417
  program2.parse();
178859
179418
  /*! Bundled license information:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexrall-code",
3
- "version": "0.5.178",
3
+ "version": "0.5.179",
4
4
  "description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
5
5
  "keywords": [
6
6
  "ai",