@nexrall/code-core 1.4.71 → 1.4.73

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 (126) hide show
  1. package/dist/agent/agentRegistry.d.ts.map +1 -1
  2. package/dist/agent/agentRegistry.js +28 -10
  3. package/dist/agent/agentTypes.d.ts.map +1 -1
  4. package/dist/agent/agentTypes.js +159 -48
  5. package/dist/agent/askOnce.d.ts.map +1 -1
  6. package/dist/agent/askOnce.js +14 -5
  7. package/dist/agent/backgroundAgents.d.ts.map +1 -1
  8. package/dist/agent/backgroundAgents.js +22 -8
  9. package/dist/agent/claimEvidence.d.ts.map +1 -1
  10. package/dist/agent/claimEvidence.js +2 -1
  11. package/dist/agent/compaction.d.ts.map +1 -1
  12. package/dist/agent/compaction.js +31 -10
  13. package/dist/agent/crossProcessLock.d.ts.map +1 -1
  14. package/dist/agent/crossProcessLock.js +14 -6
  15. package/dist/agent/editCompleteness.d.ts.map +1 -1
  16. package/dist/agent/fileLocks.d.ts.map +1 -1
  17. package/dist/agent/fileLocks.js +14 -5
  18. package/dist/agent/flaky.d.ts.map +1 -1
  19. package/dist/agent/hooks.d.ts.map +1 -1
  20. package/dist/agent/hooks.js +284 -75
  21. package/dist/agent/iterationPolicy.d.ts.map +1 -1
  22. package/dist/agent/iterationPolicy.js +26 -23
  23. package/dist/agent/lifecycleHost.d.ts.map +1 -1
  24. package/dist/agent/lifecycleHost.js +35 -11
  25. package/dist/agent/loop.d.ts +22 -4
  26. package/dist/agent/loop.d.ts.map +1 -1
  27. package/dist/agent/loop.js +238 -51
  28. package/dist/agent/memory.d.ts.map +1 -1
  29. package/dist/agent/memory.js +13 -7
  30. package/dist/agent/modelCatalogue.d.ts.map +1 -1
  31. package/dist/agent/modelCatalogue.js +4 -1
  32. package/dist/agent/nestedInstructions.d.ts +122 -0
  33. package/dist/agent/nestedInstructions.d.ts.map +1 -0
  34. package/dist/agent/nestedInstructions.js +696 -0
  35. package/dist/agent/peerRegistry.d.ts.map +1 -1
  36. package/dist/agent/peerRegistry.js +44 -6
  37. package/dist/agent/peerTransport.d.ts.map +1 -1
  38. package/dist/agent/peerTransport.js +18 -6
  39. package/dist/agent/planMode.d.ts.map +1 -1
  40. package/dist/agent/planMode.js +194 -41
  41. package/dist/agent/readDedupe.d.ts.map +1 -1
  42. package/dist/agent/readDedupe.js +3 -1
  43. package/dist/agent/securityLint.d.ts.map +1 -1
  44. package/dist/agent/sharedTasks.d.ts.map +1 -1
  45. package/dist/agent/sharedTasks.js +20 -5
  46. package/dist/agent/skills.d.ts +79 -0
  47. package/dist/agent/skills.d.ts.map +1 -1
  48. package/dist/agent/skills.js +403 -14
  49. package/dist/agent/subAgentBudget.d.ts.map +1 -1
  50. package/dist/agent/subTask.d.ts.map +1 -1
  51. package/dist/agent/subTask.js +87 -37
  52. package/dist/agent/subTaskSupport.d.ts.map +1 -1
  53. package/dist/agent/subTaskSupport.js +43 -14
  54. package/dist/agent/testIntegrity.d.ts.map +1 -1
  55. package/dist/agent/testIntegrity.js +4 -2
  56. package/dist/agent/toolDescriptions.d.ts.map +1 -1
  57. package/dist/agent/toolPrefetch.d.ts.map +1 -1
  58. package/dist/agent/toolPrefetch.js +15 -11
  59. package/dist/agent/trust.d.ts.map +1 -1
  60. package/dist/agent/trust.js +6 -2
  61. package/dist/agent/worktree.d.ts.map +1 -1
  62. package/dist/agent/worktree.js +70 -16
  63. package/dist/agent/worktreeEnforcement.d.ts.map +1 -1
  64. package/dist/agent/worktreeEnforcement.js +9 -4
  65. package/dist/api/client.d.ts +6 -0
  66. package/dist/api/client.d.ts.map +1 -1
  67. package/dist/api/client.js +64 -31
  68. package/dist/auth/index.d.ts.map +1 -1
  69. package/dist/auth/index.js +3 -1
  70. package/dist/checkpoint/manager.d.ts.map +1 -1
  71. package/dist/checkpoint/manager.js +22 -7
  72. package/dist/commands/loader.d.ts.map +1 -1
  73. package/dist/commands/loader.js +6 -1
  74. package/dist/index.d.ts +2 -0
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +2 -0
  77. package/dist/mcp/client.d.ts +42 -0
  78. package/dist/mcp/client.d.ts.map +1 -1
  79. package/dist/mcp/client.js +89 -24
  80. package/dist/mcp/httpClient.d.ts +24 -1
  81. package/dist/mcp/httpClient.d.ts.map +1 -1
  82. package/dist/mcp/httpClient.js +80 -14
  83. package/dist/mcp/instructions.d.ts +11 -0
  84. package/dist/mcp/instructions.d.ts.map +1 -0
  85. package/dist/mcp/instructions.js +42 -0
  86. package/dist/mcp/manager.d.ts +35 -0
  87. package/dist/mcp/manager.d.ts.map +1 -1
  88. package/dist/mcp/manager.js +135 -24
  89. package/dist/mcp/server.d.ts.map +1 -1
  90. package/dist/mcp/server.js +48 -14
  91. package/dist/mcp/sseClient.d.ts +9 -1
  92. package/dist/mcp/sseClient.d.ts.map +1 -1
  93. package/dist/mcp/sseClient.js +43 -10
  94. package/dist/mcp/stats.d.ts.map +1 -1
  95. package/dist/permissions/bashClassify.d.ts.map +1 -1
  96. package/dist/permissions/bashClassify.js +25 -3
  97. package/dist/permissions/destructive.d.ts.map +1 -1
  98. package/dist/permissions/destructive.js +7 -2
  99. package/dist/permissions/destructiveTokens.d.ts.map +1 -1
  100. package/dist/permissions/destructiveTokens.js +104 -24
  101. package/dist/permissions/modePolicy.d.ts.map +1 -1
  102. package/dist/permissions/modePolicy.js +40 -17
  103. package/dist/permissions/rules.d.ts.map +1 -1
  104. package/dist/permissions/rules.js +7 -5
  105. package/dist/plugins/data.d.ts.map +1 -1
  106. package/dist/plugins/data.js +6 -2
  107. package/dist/plugins/index.d.ts.map +1 -1
  108. package/dist/plugins/index.js +18 -7
  109. package/dist/plugins/installer.d.ts.map +1 -1
  110. package/dist/plugins/installer.js +17 -10
  111. package/dist/plugins/sources.d.ts.map +1 -1
  112. package/dist/plugins/sources.js +9 -3
  113. package/dist/tools/executor.d.ts.map +1 -1
  114. package/dist/tools/executor.js +454 -168
  115. package/dist/tools/sandbox.d.ts.map +1 -1
  116. package/dist/tools/sandbox.js +5 -1
  117. package/dist/tools/symbols.d.ts.map +1 -1
  118. package/dist/tools/symbols.js +30 -8
  119. package/dist/tools/tsLangService.d.ts.map +1 -1
  120. package/dist/tools/tsLangService.js +12 -4
  121. package/dist/types.d.ts +17 -0
  122. package/dist/types.d.ts.map +1 -1
  123. package/dist/util/frontmatter.d.ts.map +1 -1
  124. package/dist/util/miniYaml.d.ts.map +1 -1
  125. package/dist/util/miniYaml.js +3 -1
  126. package/package.json +1 -1
@@ -85,7 +85,9 @@ function loadHooks(workDir) {
85
85
  if (fs.existsSync(p))
86
86
  fromSettings = JSON.parse(fs.readFileSync(p, 'utf-8')).hooks ?? {};
87
87
  }
88
- catch { /* ignore */ }
88
+ catch {
89
+ /* ignore */
90
+ }
89
91
  // `--settings`: hooks from the run's own settings object run after the project's.
90
92
  const sess = (0, rules_1.getSessionSettings)()?.hooks;
91
93
  if (sess && typeof sess === 'object') {
@@ -103,10 +105,7 @@ function loadHooks(workDir) {
103
105
  const extra = fromPlugins[phase];
104
106
  if (!Array.isArray(extra) || !extra.length)
105
107
  continue;
106
- merged[phase] = [
107
- ...((merged[phase]) ?? []),
108
- ...extra,
109
- ];
108
+ merged[phase] = [...(merged[phase] ?? []), ...extra];
110
109
  }
111
110
  // One normalising pass over every entry-list phase, whatever source it came from.
112
111
  for (const phase of ENTRY_LIST_PHASES) {
@@ -119,13 +118,34 @@ function loadHooks(workDir) {
119
118
  }
120
119
  /** The phases whose value is an array of ENTRIES (each carrying a matcher + handlers). */
121
120
  const ENTRY_LIST_PHASES = [
122
- 'PreToolUse', 'PostToolUse', 'SessionStart', 'UserPromptSubmit', 'PreCompact',
123
- 'PermissionRequest', 'Notification', 'PostToolUseFailure', 'SubagentStart',
124
- 'PostCompact', 'SessionEnd', 'FileChanged', 'InstructionsLoaded', 'StopFailure',
125
- 'PermissionDenied', 'PreModelSwitch', 'PostModelSwitch', 'CwdChanged', 'DirectoryAdded',
126
- 'ConfigChange', 'WorktreeCreate', 'WorktreeRemove', 'Setup',
127
- 'PostToolBatch', 'TaskCreated', 'TaskCompleted',
128
- 'Elicitation', 'ElicitationResult',
121
+ 'PreToolUse',
122
+ 'PostToolUse',
123
+ 'SessionStart',
124
+ 'UserPromptSubmit',
125
+ 'PreCompact',
126
+ 'PermissionRequest',
127
+ 'Notification',
128
+ 'PostToolUseFailure',
129
+ 'SubagentStart',
130
+ 'PostCompact',
131
+ 'SessionEnd',
132
+ 'FileChanged',
133
+ 'InstructionsLoaded',
134
+ 'StopFailure',
135
+ 'PermissionDenied',
136
+ 'PreModelSwitch',
137
+ 'PostModelSwitch',
138
+ 'CwdChanged',
139
+ 'DirectoryAdded',
140
+ 'ConfigChange',
141
+ 'WorktreeCreate',
142
+ 'WorktreeRemove',
143
+ 'Setup',
144
+ 'PostToolBatch',
145
+ 'TaskCreated',
146
+ 'TaskCompleted',
147
+ 'Elicitation',
148
+ 'ElicitationResult',
129
149
  ];
130
150
  /**
131
151
  * Accept BOTH config shapes for the same list, so a hook can never be silently dropped:
@@ -159,11 +179,17 @@ const _hookDeliveries = [];
159
179
  let _hookWakeListener = null;
160
180
  let _hookStatusListener = null;
161
181
  /** Called (if set) the moment a wake lands, so an idle host can react at once. */
162
- function setHookWakeListener(fn) { _hookWakeListener = fn; }
182
+ function setHookWakeListener(fn) {
183
+ _hookWakeListener = fn;
184
+ }
163
185
  /** Where `statusMessage` goes. The message is null when no hook is running. */
164
- function setHookStatusListener(fn) { _hookStatusListener = fn; }
186
+ function setHookStatusListener(fn) {
187
+ _hookStatusListener = fn;
188
+ }
165
189
  /** Everything queued so far — the loop calls this before each request. */
166
- function drainHookDeliveries() { return _hookDeliveries.splice(0); }
190
+ function drainHookDeliveries() {
191
+ return _hookDeliveries.splice(0);
192
+ }
167
193
  /** Only the wakes — an idle host must not start a turn just for async context. */
168
194
  function takeHookWakes() {
169
195
  const out = _hookDeliveries.filter((d) => d.kind === 'wake');
@@ -172,7 +198,9 @@ function takeHookWakes() {
172
198
  _hookDeliveries.splice(i, 1);
173
199
  return out;
174
200
  }
175
- function hasPendingHookWake() { return _hookDeliveries.some((d) => d.kind === 'wake'); }
201
+ function hasPendingHookWake() {
202
+ return _hookDeliveries.some((d) => d.kind === 'wake');
203
+ }
176
204
  /** The text a delivery batch becomes in the conversation — the same envelope sync hooks use. */
177
205
  function formatHookDeliveries(list) {
178
206
  return list.map((d) => `<hook-context source="${d.source}">\n${d.text}\n</hook-context>`).join('\n\n');
@@ -189,7 +217,9 @@ function hookIdentity(event, hook, matcher, dir) {
189
217
  return `${dir ?? ''}|${event}|${matcher ?? ''}|${hook.plugin ?? ''}|${body}`;
190
218
  }
191
219
  /** Forget which `once` hooks have run (a new session: /clear, a fresh run). */
192
- function resetHookOnceState() { _hookOnceDone.clear(); }
220
+ function resetHookOnceState() {
221
+ _hookOnceDone.clear();
222
+ }
193
223
  // While any hook with a statusMessage is running the UI shows the NEWEST of those
194
224
  // messages — null once they all finish. Ref-counted because parallel sub-agents run
195
225
  // hooks concurrently, and "the first hook to finish" must not clear the label of one
@@ -213,22 +243,30 @@ function hookStatusEnd(id) {
213
243
  }
214
244
  let _hookMcpCaller = null;
215
245
  /** Hosts with connected MCP servers register their caller once; null = mcp_tool hooks report "no MCP client". */
216
- function setHookMcpCaller(fn) { _hookMcpCaller = fn; }
246
+ function setHookMcpCaller(fn) {
247
+ _hookMcpCaller = fn;
248
+ }
217
249
  let _hookEventListener = null;
218
250
  /**
219
251
  * Install (or clear) the sink that receives every hook run's lifecycle records.
220
252
  * One per process: hooks fire from the loop, the lifecycle host and the permission
221
253
  * gate, but they all belong to the same session's stream.
222
254
  */
223
- function setHookEventListener(fn) { _hookEventListener = fn; }
255
+ function setHookEventListener(fn) {
256
+ _hookEventListener = fn;
257
+ }
224
258
  /** True when a sink is installed, for hosts skipping work that would only feed the stream. */
225
- function hasHookEventListener() { return _hookEventListener !== null; }
259
+ function hasHookEventListener() {
260
+ return _hookEventListener !== null;
261
+ }
226
262
  let _hookIdSeq = 0;
227
263
  function emitHookLifecycle(evt) {
228
264
  try {
229
265
  _hookEventListener?.(evt);
230
266
  }
231
- catch { /* the host's listener must not break the hook */ }
267
+ catch {
268
+ /* the host's listener must not break the hook */
269
+ }
232
270
  }
233
271
  /** The stream's hook_name: what the handler IS, in the config author's terms. */
234
272
  function hookDisplayName(hook) {
@@ -276,7 +314,15 @@ async function runWithHookEvents(hook, event, run) {
276
314
  pendingOut = '';
277
315
  pendingErr = '';
278
316
  lastFlush = Date.now();
279
- emitHookLifecycle({ kind: 'progress', hookId, hookName, hookEvent: event, stdout, stderr, output: stdout + stderr });
317
+ emitHookLifecycle({
318
+ kind: 'progress',
319
+ hookId,
320
+ hookName,
321
+ hookEvent: event,
322
+ stdout,
323
+ stderr,
324
+ output: stdout + stderr,
325
+ });
280
326
  };
281
327
  const onData = (d) => {
282
328
  if (Date.now() - startedAt < HOOK_PROGRESS_AFTER_MS)
@@ -301,7 +347,16 @@ async function runWithHookEvents(hook, event, run) {
301
347
  if (flushTimer)
302
348
  clearTimeout(flushTimer);
303
349
  const message = err?.message ?? String(err);
304
- emitHookLifecycle({ kind: 'response', hookId, hookName, hookEvent: event, stdout: '', stderr: message, output: message, outcome: 'error' });
350
+ emitHookLifecycle({
351
+ kind: 'response',
352
+ hookId,
353
+ hookName,
354
+ hookEvent: event,
355
+ stdout: '',
356
+ stderr: message,
357
+ output: message,
358
+ outcome: 'error',
359
+ });
305
360
  throw err;
306
361
  }
307
362
  if (flushTimer)
@@ -349,11 +404,17 @@ function startBackgroundHook(hook, payload, opts) {
349
404
  const out = (r.stdout ?? '').toString().trim();
350
405
  if (hook.asyncRewake && r.status === 2) {
351
406
  const text = (r.stderr ?? '').toString().trim() || out;
352
- _hookDeliveries.push({ kind: 'wake', text: text || 'Hook asked the session to wake (exit 2).', source: opts.eventLabel });
407
+ _hookDeliveries.push({
408
+ kind: 'wake',
409
+ text: text || 'Hook asked the session to wake (exit 2).',
410
+ source: opts.eventLabel,
411
+ });
353
412
  try {
354
413
  _hookWakeListener?.();
355
414
  }
356
- catch { /* the host's listener must not break the hook */ }
415
+ catch {
416
+ /* the host's listener must not break the hook */
417
+ }
357
418
  return;
358
419
  }
359
420
  const context = directiveContext(out);
@@ -362,8 +423,12 @@ function startBackgroundHook(hook, payload, opts) {
362
423
  };
363
424
  // Through runHookHandler, not spawnHook directly: background runs get the same
364
425
  // start/finish lifecycle records, and command dispatch stays in one place.
365
- void runHookHandler(hook, payload, { cwd: opts.cwd, timeoutMs: opts.timeoutMs, env: opts.env, event: opts.event })
366
- .then(done, () => done({ status: null, stdout: '', stderr: '' }));
426
+ void runHookHandler(hook, payload, {
427
+ cwd: opts.cwd,
428
+ timeoutMs: opts.timeoutMs,
429
+ env: opts.env,
430
+ event: opts.event,
431
+ }).then(done, () => done({ status: null, stdout: '', stderr: '' }));
367
432
  }
368
433
  /** additionalContext + systemMessage from a background hook's JSON stdout — both go to the model. */
369
434
  function directiveContext(out) {
@@ -410,7 +475,12 @@ function spawnHook(command, opts) {
410
475
  };
411
476
  let child;
412
477
  try {
413
- child = (0, child_process_1.spawn)(command, { shell: true, cwd: opts.cwd, env: opts.env ?? process.env, stdio: ['pipe', 'pipe', 'pipe'] });
478
+ child = (0, child_process_1.spawn)(command, {
479
+ shell: true,
480
+ cwd: opts.cwd,
481
+ env: opts.env ?? process.env,
482
+ stdio: ['pipe', 'pipe', 'pipe'],
483
+ });
414
484
  }
415
485
  catch {
416
486
  resolve({ status: null, stdout: '', stderr: '' });
@@ -419,10 +489,17 @@ function spawnHook(command, opts) {
419
489
  // timeout === null = no timer at all: plain `async` hooks are not bounded
420
490
  // (Claude Code enforces no timeout on them), and a clamped setTimeout would
421
491
  // fire immediately and kill the hook instead of never firing.
422
- const timer = opts.timeout === null ? null : setTimeout(() => { try {
423
- child.kill('SIGTERM');
424
- }
425
- catch { /* gone */ } done(null); }, opts.timeout);
492
+ const timer = opts.timeout === null
493
+ ? null
494
+ : setTimeout(() => {
495
+ try {
496
+ child.kill('SIGTERM');
497
+ }
498
+ catch {
499
+ /* gone */
500
+ }
501
+ done(null);
502
+ }, opts.timeout);
426
503
  const cap = 16 * 1024 * 1024;
427
504
  // `onData` is how `--include-hook-events` sees a long hook's output as it
428
505
  // arrives; without such a listener it is undefined and costs one check.
@@ -440,7 +517,9 @@ function spawnHook(command, opts) {
440
517
  });
441
518
  child.on('error', () => done(null));
442
519
  child.on('close', (code) => done(code));
443
- child.stdin?.on('error', () => { });
520
+ child.stdin?.on('error', () => {
521
+ /* hook ignored stdin */
522
+ });
444
523
  child.stdin?.end(opts.input ?? '');
445
524
  });
446
525
  }
@@ -463,7 +542,7 @@ async function httpHook(hook, payload, timeoutMs) {
463
542
  for (const [k, v] of Object.entries(hook.headers ?? {})) {
464
543
  // Unlisted variables expand to nothing rather than leaking the whole environment
465
544
  // into a request header.
466
- headers[k] = String(v).replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_m, a1, a2) => (allowed.has(a1 ?? a2) ? (process.env[a1 ?? a2] ?? '') : ''));
545
+ headers[k] = String(v).replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_m, a1, a2) => allowed.has(a1 ?? a2) ? (process.env[a1 ?? a2] ?? '') : '');
467
546
  }
468
547
  try {
469
548
  const res = await fetch(url, { method: 'POST', headers, body: payload, signal: AbortSignal.timeout(timeoutMs) });
@@ -526,7 +605,9 @@ function mcpHookInput(input, payload) {
526
605
  if (j && typeof j === 'object' && !Array.isArray(j))
527
606
  root = j;
528
607
  }
529
- catch { /* non-JSON payload: nothing to substitute from */ }
608
+ catch {
609
+ /* non-JSON payload: nothing to substitute from */
610
+ }
530
611
  const nav = (parts) => {
531
612
  let cur = root;
532
613
  for (const p of parts) {
@@ -537,7 +618,10 @@ function mcpHookInput(input, payload) {
537
618
  return cur;
538
619
  };
539
620
  const lookup = (path) => {
540
- const parts = path.split('.').map((s) => s.trim()).filter(Boolean);
621
+ const parts = path
622
+ .split('.')
623
+ .map((s) => s.trim())
624
+ .filter(Boolean);
541
625
  if (!parts.length)
542
626
  return undefined;
543
627
  const candidates = [parts];
@@ -591,7 +675,13 @@ async function runHookHandler(hook, payload, opts) {
591
675
  if (hook.type === 'prompt' || hook.type === 'agent') {
592
676
  return promptHook(hook, payload, { ...opts, timeoutMs: opts.timeoutMs ?? 60000, envCtx: opts.hookEnv });
593
677
  }
594
- return spawnHook(String(hook.command ?? ''), { cwd: opts.cwd, timeout: opts.timeoutMs, input: payload, env, onData });
678
+ return spawnHook(String(hook.command ?? ''), {
679
+ cwd: opts.cwd,
680
+ timeout: opts.timeoutMs,
681
+ input: payload,
682
+ env,
683
+ onData,
684
+ });
595
685
  });
596
686
  }
597
687
  // ─── Prompt / agent hooks ──────────────────────────────────────────────────────
@@ -646,7 +736,11 @@ const HOOK_AGENT_TOOLS = [
646
736
  {
647
737
  name: 'glob',
648
738
  description: 'Find files matching a glob pattern, e.g. "**/*.ts".',
649
- input_schema: { type: 'object', properties: { pattern: { type: 'string' }, path: { type: 'string' } }, required: ['pattern'] },
739
+ input_schema: {
740
+ type: 'object',
741
+ properties: { pattern: { type: 'string' }, path: { type: 'string' } },
742
+ required: ['pattern'],
743
+ },
650
744
  },
651
745
  ];
652
746
  /** A model name that cannot exist — the allowlist filter then sends NO tools at all. */
@@ -674,7 +768,7 @@ function parsePromptHookReply(reply) {
674
768
  }
675
769
  }
676
770
  const PROMPT_HOOK_RULES = 'You are a hook evaluator for a coding agent. The hook input JSON is given below, followed by ' +
677
- 'the hook\'s own instruction. Decide whether the agent action described in the input should ' +
771
+ "the hook's own instruction. Decide whether the agent action described in the input should " +
678
772
  'proceed. Reply with ONLY one JSON object and nothing else: {"ok":true} to allow, or ' +
679
773
  '{"ok":false,"reason":"short explanation"} to block.';
680
774
  async function promptHook(hook, payload, opts) {
@@ -682,15 +776,22 @@ async function promptHook(hook, payload, opts) {
682
776
  if (!instruction)
683
777
  return { status: 1, stdout: '', stderr: 'prompt hook has no "prompt"' };
684
778
  // `\$ARGUMENTS` escapes the substitution (Claude Code's own rule).
685
- // eslint-disable-next-line no-control-regex -- U+0001 sentinel protects a literal $ARGUMENTS from being substituted twice
686
- const body = instruction.replace(/\\\$ARGUMENTS/g, '\u0001').replace(/\$ARGUMENTS/g, payload).replace(/\u0001/g, '$ARGUMENTS');
779
+ const body = instruction
780
+ .replace(/\\\$ARGUMENTS/g, '\u0001')
781
+ .replace(/\$ARGUMENTS/g, payload)
782
+ // eslint-disable-next-line no-control-regex -- U+0001 sentinel protects a literal $ARGUMENTS from being substituted twice
783
+ .replace(/\u0001/g, '$ARGUMENTS');
687
784
  // Abort bridge: streamChat polls a plain {aborted} object, and a hook must never hang
688
785
  // the turn — the timer flips it at the hook's own timeout, the outer signal aborts too.
689
786
  const ctl = { aborted: false };
690
- const timer = setTimeout(() => { ctl.aborted = true; }, Math.max(1000, opts.timeoutMs));
787
+ const timer = setTimeout(() => {
788
+ ctl.aborted = true;
789
+ }, Math.max(1000, opts.timeoutMs));
691
790
  const outerPoll = opts.abortSignal
692
- ? setInterval(() => { if (opts.abortSignal?.aborted)
693
- ctl.aborted = true; }, 100)
791
+ ? setInterval(() => {
792
+ if (opts.abortSignal?.aborted)
793
+ ctl.aborted = true;
794
+ }, 100)
694
795
  : undefined;
695
796
  if (opts.abortSignal?.aborted) {
696
797
  clearTimeout(timer);
@@ -707,10 +808,17 @@ async function promptHook(hook, payload, opts) {
707
808
  clearInterval(outerPoll);
708
809
  }
709
810
  async function runModel() {
710
- const messages = [{
811
+ const messages = [
812
+ {
711
813
  role: 'user',
712
- content: [{ type: 'text', text: `${PROMPT_HOOK_RULES}\n\nHook input:\n${payload}\n\nHook instruction:\n${body}` }],
713
- }];
814
+ content: [
815
+ {
816
+ type: 'text',
817
+ text: `${PROMPT_HOOK_RULES}\n\nHook input:\n${payload}\n\nHook instruction:\n${body}`,
818
+ },
819
+ ],
820
+ },
821
+ ];
714
822
  const isAgent = hook.type === 'agent';
715
823
  try {
716
824
  let replyText = '';
@@ -724,11 +832,18 @@ async function promptHook(hook, payload, opts) {
724
832
  // read-only tools and a prompt hook sees none (the marker matches nothing).
725
833
  toolAllowlist: isAgent ? HOOK_AGENT_TOOLS.map((t) => t.name) : [NO_TOOLS_MARKER],
726
834
  extraTools: isAgent ? HOOK_AGENT_TOOLS : undefined,
727
- }, () => { });
728
- const blocks = Array.isArray(msg.content) ? msg.content : [];
835
+ }, () => {
836
+ /* streaming not needed; the final message carries the text */
837
+ });
838
+ const blocks = Array.isArray(msg.content)
839
+ ? msg.content
840
+ : [];
729
841
  const toolUses = blocks.filter((b) => b.type === 'tool_use');
730
842
  if (!toolUses.length || !isAgent) {
731
- replyText = blocks.filter((b) => b.type === 'text' && typeof b.text === 'string').map((b) => b.text).join('\n');
843
+ replyText = blocks
844
+ .filter((b) => b.type === 'text' && typeof b.text === 'string')
845
+ .map((b) => b.text)
846
+ .join('\n');
732
847
  break;
733
848
  }
734
849
  // Run the read-only tool round and feed the results back.
@@ -739,7 +854,9 @@ async function promptHook(hook, payload, opts) {
739
854
  try {
740
855
  out = getHookToolExecutors().includes(String(tu.name))
741
856
  ? await (0, executor_1.executeTool)(String(tu.name), tu.input ?? {}, ctl, undefined, opts.cwd)
742
- : { error: `Tool ${String(tu.name)} is not available to a hook — only ${getHookToolExecutors().join(', ')}.` };
857
+ : {
858
+ error: `Tool ${String(tu.name)} is not available to a hook — only ${getHookToolExecutors().join(', ')}.`,
859
+ };
743
860
  }
744
861
  catch (err) {
745
862
  out = { error: err.message || 'tool failed' };
@@ -747,7 +864,11 @@ async function promptHook(hook, payload, opts) {
747
864
  results.push({
748
865
  type: 'tool_result',
749
866
  tool_use_id: String(tu.id),
750
- content: out.error ? (out.output ? `Error: ${out.error}\n\n${out.output}` : `Error: ${out.error}`) : (out.output ?? ''),
867
+ content: out.error
868
+ ? out.output
869
+ ? `Error: ${out.error}\n\n${out.output}`
870
+ : `Error: ${out.error}`
871
+ : (out.output ?? ''),
751
872
  is_error: out.error !== undefined,
752
873
  });
753
874
  }
@@ -763,7 +884,9 @@ async function promptHook(hook, payload, opts) {
763
884
  if (decided)
764
885
  return { status: 2, stdout: '', stderr: parsed.reason || `Blocked by ${hook.type} hook` };
765
886
  // Allow — carry additionalContext through the same JSON channel callers parse.
766
- const passthrough = parsed.additionalContext ? JSON.stringify({ additionalContext: parsed.additionalContext }) : '';
887
+ const passthrough = parsed.additionalContext
888
+ ? JSON.stringify({ additionalContext: parsed.additionalContext })
889
+ : '';
767
890
  return { status: 0, stdout: passthrough, stderr: '' };
768
891
  }
769
892
  catch (err) {
@@ -842,7 +965,11 @@ function hookTimeoutMs(hook, fallbackMs = 60000) {
842
965
  function hookMatches(matcher, toolName) {
843
966
  if (!matcher || matcher === '*')
844
967
  return true;
845
- return matcher.split('|').map((m) => m.trim()).filter(Boolean).some((alt) => {
968
+ return matcher
969
+ .split('|')
970
+ .map((m) => m.trim())
971
+ .filter(Boolean)
972
+ .some((alt) => {
846
973
  const norm = (0, agentTypes_1.normaliseToolName)(alt);
847
974
  return norm === toolName || (norm === alt && toolName.includes(alt));
848
975
  });
@@ -914,7 +1041,9 @@ async function runToolHooks(entries, phase, toolName, input, workDir, result, ho
914
1041
  outcome.context = (outcome.context ? outcome.context + '\n' : '') + j.additionalContext;
915
1042
  }
916
1043
  }
917
- catch { /* not a directive — ignore */ }
1044
+ catch {
1045
+ /* not a directive — ignore */
1046
+ }
918
1047
  }
919
1048
  // Exit code 2 → hard block; stderr is the reason fed back to the model
920
1049
  if (r.status === 2) {
@@ -941,10 +1070,23 @@ async function runSimpleHooks(defs, workDir, extraEnv, hookOpts, event = 'hook')
941
1070
  if (gate.skip)
942
1071
  continue;
943
1072
  if (gate.background) {
944
- startBackgroundHook(hook, '', { cwd: workDir, env, eventLabel: event, event, timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 10000) : null, key: gate.key });
1073
+ startBackgroundHook(hook, '', {
1074
+ cwd: workDir,
1075
+ env,
1076
+ eventLabel: event,
1077
+ event,
1078
+ timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 10000) : null,
1079
+ key: gate.key,
1080
+ });
945
1081
  continue;
946
1082
  }
947
- const r = await runHookHandler(hook, '', { cwd: workDir, timeoutMs: hookTimeoutMs(hook, 10000), env, event, ...hookOpts });
1083
+ const r = await runHookHandler(hook, '', {
1084
+ cwd: workDir,
1085
+ timeoutMs: hookTimeoutMs(hook, 10000),
1086
+ env,
1087
+ event,
1088
+ ...hookOpts,
1089
+ });
948
1090
  if (gate.statusId !== undefined)
949
1091
  hookStatusEnd(gate.statusId);
950
1092
  if (r.status === 0 && gate.key)
@@ -964,7 +1106,13 @@ function flattenHookDefs(items, matchValue) {
964
1106
  const m = it.matcher;
965
1107
  // For these events the matcher (if any) selects on a plain value (e.g. the compact
966
1108
  // trigger "auto"/"manual"); empty or "*" matches all.
967
- if (m && m !== '*' && matchValue !== undefined && !m.split('|').map((x) => x.trim()).includes(matchValue))
1109
+ if (m &&
1110
+ m !== '*' &&
1111
+ matchValue !== undefined &&
1112
+ !m
1113
+ .split('|')
1114
+ .map((x) => x.trim())
1115
+ .includes(matchValue))
968
1116
  continue;
969
1117
  out.push(...it.hooks);
970
1118
  }
@@ -1013,19 +1161,30 @@ async function runLifecycleHooks(items, event, workDir, payload, matchValue, hoo
1013
1161
  continue;
1014
1162
  }
1015
1163
  const timeout = hookTimeoutMs(hook, hook.type === 'agent' ? 60000 : hook.type === 'prompt' ? 30000 : 30000);
1016
- const r = await runHookHandler(hook, input, { cwd: workDir, timeoutMs: timeout, env: { ...process.env, NEXRALL_HOOK_PHASE: event }, event, ...hookOpts });
1164
+ const r = await runHookHandler(hook, input, {
1165
+ cwd: workDir,
1166
+ timeoutMs: timeout,
1167
+ env: { ...process.env, NEXRALL_HOOK_PHASE: event },
1168
+ event,
1169
+ ...hookOpts,
1170
+ });
1017
1171
  const out = (r.stdout ?? '').toString().trim();
1018
1172
  if (out.startsWith('{')) {
1019
1173
  try {
1020
1174
  const j = JSON.parse(out);
1021
1175
  // PermissionRequest answers, in nex's flat form or Claude Code's hookSpecificOutput form.
1022
1176
  if (event === 'PermissionRequest') {
1023
- const d = j.hookSpecificOutput?.decision?.behavior ?? (typeof j.decision === 'string' ? j.decision : j.decision?.behavior);
1177
+ const d = j.hookSpecificOutput?.decision?.behavior ??
1178
+ (typeof j.decision === 'string' ? j.decision : j.decision?.behavior);
1024
1179
  if (d === 'allow' || d === 'approve')
1025
1180
  outcome.allow = true;
1026
1181
  else if (d === 'deny' || d === 'block') {
1027
1182
  outcome.block = true;
1028
- outcome.reason = j.reason ?? j.hookSpecificOutput?.decision?.message ?? outcome.reason ?? 'Denied by PermissionRequest hook';
1183
+ outcome.reason =
1184
+ j.reason ??
1185
+ j.hookSpecificOutput?.decision?.message ??
1186
+ outcome.reason ??
1187
+ 'Denied by PermissionRequest hook';
1029
1188
  }
1030
1189
  }
1031
1190
  if (j.decision === 'block') {
@@ -1040,7 +1199,9 @@ async function runLifecycleHooks(items, event, workDir, payload, matchValue, hoo
1040
1199
  if (typeof j.systemMessage === 'string' && j.systemMessage)
1041
1200
  outcome.systemMessage = j.systemMessage;
1042
1201
  }
1043
- catch { /* not a directive */ }
1202
+ catch {
1203
+ /* not a directive */
1204
+ }
1044
1205
  }
1045
1206
  else if (out && (event === 'SessionStart' || event === 'UserPromptSubmit') && r.status === 0) {
1046
1207
  // Claude Code parity: plain stdout from these two events is added as context.
@@ -1074,7 +1235,9 @@ async function runPermissionRequestHooks(workDir, tool, input, sessionId = '') {
1074
1235
  if (o.allow)
1075
1236
  return { answer: 'allow' };
1076
1237
  }
1077
- catch { /* a hook must never break the prompt */ }
1238
+ catch {
1239
+ /* a hook must never break the prompt */
1240
+ }
1078
1241
  return { answer: null };
1079
1242
  }
1080
1243
  /** `hookSpecificOutput:{action,content}` (Claude Code's shape) or the flat form; null = no answer. */
@@ -1110,7 +1273,13 @@ async function runElicitationHooks(workDir, server, input, sessionId = '') {
1110
1273
  const defs = flattenHookDefs(items, server);
1111
1274
  if (!defs.length)
1112
1275
  return null;
1113
- const payload = JSON.stringify({ session_id: sessionId, cwd: workDir, hook_event_name: 'Elicitation', mcp_server_name: server, ...input });
1276
+ const payload = JSON.stringify({
1277
+ session_id: sessionId,
1278
+ cwd: workDir,
1279
+ hook_event_name: 'Elicitation',
1280
+ mcp_server_name: server,
1281
+ ...input,
1282
+ });
1114
1283
  for (const hook of defs) {
1115
1284
  if (!isRunnableHook(hook))
1116
1285
  continue;
@@ -1118,10 +1287,22 @@ async function runElicitationHooks(workDir, server, input, sessionId = '') {
1118
1287
  if (gate.skip)
1119
1288
  continue;
1120
1289
  if (gate.background) {
1121
- startBackgroundHook(hook, payload, { cwd: workDir, env: { ...process.env, NEXRALL_HOOK_PHASE: 'Elicitation' }, eventLabel: 'Elicitation', event: 'Elicitation', timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 60000) : null, key: gate.key });
1290
+ startBackgroundHook(hook, payload, {
1291
+ cwd: workDir,
1292
+ env: { ...process.env, NEXRALL_HOOK_PHASE: 'Elicitation' },
1293
+ eventLabel: 'Elicitation',
1294
+ event: 'Elicitation',
1295
+ timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 60000) : null,
1296
+ key: gate.key,
1297
+ });
1122
1298
  continue;
1123
1299
  }
1124
- const r = await runHookHandler(hook, payload, { cwd: workDir, timeoutMs: hookTimeoutMs(hook, 30000), env: { ...process.env, NEXRALL_HOOK_PHASE: 'Elicitation' }, event: 'Elicitation' });
1300
+ const r = await runHookHandler(hook, payload, {
1301
+ cwd: workDir,
1302
+ timeoutMs: hookTimeoutMs(hook, 30000),
1303
+ env: { ...process.env, NEXRALL_HOOK_PHASE: 'Elicitation' },
1304
+ event: 'Elicitation',
1305
+ });
1125
1306
  if (gate.statusId !== undefined)
1126
1307
  hookStatusEnd(gate.statusId);
1127
1308
  if (r.status === 0 && gate.key)
@@ -1133,7 +1314,9 @@ async function runElicitationHooks(workDir, server, input, sessionId = '') {
1133
1314
  return answer;
1134
1315
  }
1135
1316
  }
1136
- catch { /* a hook must never break elicitation */ }
1317
+ catch {
1318
+ /* a hook must never break elicitation */
1319
+ }
1137
1320
  return null;
1138
1321
  }
1139
1322
  /**
@@ -1157,14 +1340,32 @@ async function runElicitationResultHooks(workDir, server, answer, meta = {}) {
1157
1340
  if (gate.skip)
1158
1341
  continue;
1159
1342
  const payload = JSON.stringify({
1160
- session_id: meta.sessionId ?? '', cwd: workDir, hook_event_name: 'ElicitationResult', mcp_server_name: server,
1161
- action: current.action, content: current.content, mode: meta.mode, elicitation_id: meta.elicitationId,
1343
+ session_id: meta.sessionId ?? '',
1344
+ cwd: workDir,
1345
+ hook_event_name: 'ElicitationResult',
1346
+ mcp_server_name: server,
1347
+ action: current.action,
1348
+ content: current.content,
1349
+ mode: meta.mode,
1350
+ elicitation_id: meta.elicitationId,
1162
1351
  });
1163
1352
  if (gate.background) {
1164
- startBackgroundHook(hook, payload, { cwd: workDir, env: { ...process.env, NEXRALL_HOOK_PHASE: 'ElicitationResult' }, eventLabel: 'ElicitationResult', event: 'ElicitationResult', timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 60000) : null, key: gate.key });
1353
+ startBackgroundHook(hook, payload, {
1354
+ cwd: workDir,
1355
+ env: { ...process.env, NEXRALL_HOOK_PHASE: 'ElicitationResult' },
1356
+ eventLabel: 'ElicitationResult',
1357
+ event: 'ElicitationResult',
1358
+ timeoutMs: hook.asyncRewake ? hookTimeoutMs(hook, 60000) : null,
1359
+ key: gate.key,
1360
+ });
1165
1361
  continue;
1166
1362
  }
1167
- const r = await runHookHandler(hook, payload, { cwd: workDir, timeoutMs: hookTimeoutMs(hook, 30000), env: { ...process.env, NEXRALL_HOOK_PHASE: 'ElicitationResult' }, event: 'ElicitationResult' });
1363
+ const r = await runHookHandler(hook, payload, {
1364
+ cwd: workDir,
1365
+ timeoutMs: hookTimeoutMs(hook, 30000),
1366
+ env: { ...process.env, NEXRALL_HOOK_PHASE: 'ElicitationResult' },
1367
+ event: 'ElicitationResult',
1368
+ });
1168
1369
  if (gate.statusId !== undefined)
1169
1370
  hookStatusEnd(gate.statusId);
1170
1371
  if (r.status === 0 && gate.key)
@@ -1213,9 +1414,13 @@ function fireNotificationHooks(workDir, message, type = 'permission_prompt') {
1213
1414
  const items = loadHooks(workDir).Notification;
1214
1415
  if (!items?.length)
1215
1416
  return;
1216
- void runLifecycleHooks(items, 'Notification', workDir, { message, notification_type: type }).catch(() => { });
1417
+ void runLifecycleHooks(items, 'Notification', workDir, { message, notification_type: type }).catch(() => {
1418
+ /* a hook must never break the prompt */
1419
+ });
1420
+ }
1421
+ catch {
1422
+ /* ignore */
1217
1423
  }
1218
- catch { /* ignore */ }
1219
1424
  }
1220
1425
  /** For hosts that run their own manual /compact: fire PreCompact (trigger "manual"), awaited, never blocking. */
1221
1426
  /** The manual /compact counterpart of the auto PostCompact hook (trigger "manual"). */
@@ -1226,7 +1431,9 @@ async function fireManualPostCompactHook(workDir, sessionId, messagesSummarized)
1226
1431
  return;
1227
1432
  await runLifecycleHooks(items, 'PostCompact', workDir, { trigger: 'manual', session_id: sessionId, messages_summarized: messagesSummarized }, 'manual');
1228
1433
  }
1229
- catch { /* a hook must never break compaction */ }
1434
+ catch {
1435
+ /* a hook must never break compaction */
1436
+ }
1230
1437
  }
1231
1438
  async function fireManualPreCompactHook(workDir, sessionId, messagesToSummarize) {
1232
1439
  try {
@@ -1235,6 +1442,8 @@ async function fireManualPreCompactHook(workDir, sessionId, messagesToSummarize)
1235
1442
  return;
1236
1443
  await runLifecycleHooks(items, 'PreCompact', workDir, { trigger: 'manual', session_id: sessionId, messages_to_summarize: messagesToSummarize }, 'manual');
1237
1444
  }
1238
- catch { /* a hook must never break compaction */ }
1445
+ catch {
1446
+ /* a hook must never break compaction */
1447
+ }
1239
1448
  }
1240
1449
  //# sourceMappingURL=hooks.js.map