@nexrall/code-core 1.4.70 → 1.4.72

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 (125) 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 +15 -7
  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 -74
  21. package/dist/agent/iterationPolicy.d.ts.map +1 -1
  22. package/dist/agent/iterationPolicy.js +28 -25
  23. package/dist/agent/lifecycleHost.d.ts.map +1 -1
  24. package/dist/agent/lifecycleHost.js +36 -12
  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 +223 -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 +5 -3
  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.map +1 -1
  66. package/dist/api/client.js +67 -32
  67. package/dist/auth/index.d.ts.map +1 -1
  68. package/dist/auth/index.js +3 -1
  69. package/dist/checkpoint/manager.d.ts.map +1 -1
  70. package/dist/checkpoint/manager.js +23 -8
  71. package/dist/commands/loader.d.ts.map +1 -1
  72. package/dist/commands/loader.js +6 -1
  73. package/dist/index.d.ts +2 -0
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +2 -0
  76. package/dist/mcp/client.d.ts +34 -0
  77. package/dist/mcp/client.d.ts.map +1 -1
  78. package/dist/mcp/client.js +89 -24
  79. package/dist/mcp/httpClient.d.ts +24 -1
  80. package/dist/mcp/httpClient.d.ts.map +1 -1
  81. package/dist/mcp/httpClient.js +80 -14
  82. package/dist/mcp/instructions.d.ts +11 -0
  83. package/dist/mcp/instructions.d.ts.map +1 -0
  84. package/dist/mcp/instructions.js +42 -0
  85. package/dist/mcp/manager.d.ts +24 -0
  86. package/dist/mcp/manager.d.ts.map +1 -1
  87. package/dist/mcp/manager.js +117 -24
  88. package/dist/mcp/server.d.ts.map +1 -1
  89. package/dist/mcp/server.js +48 -14
  90. package/dist/mcp/sseClient.d.ts +9 -1
  91. package/dist/mcp/sseClient.d.ts.map +1 -1
  92. package/dist/mcp/sseClient.js +43 -10
  93. package/dist/mcp/stats.d.ts.map +1 -1
  94. package/dist/permissions/bashClassify.d.ts.map +1 -1
  95. package/dist/permissions/bashClassify.js +25 -3
  96. package/dist/permissions/destructive.d.ts.map +1 -1
  97. package/dist/permissions/destructive.js +7 -2
  98. package/dist/permissions/destructiveTokens.d.ts.map +1 -1
  99. package/dist/permissions/destructiveTokens.js +104 -24
  100. package/dist/permissions/modePolicy.d.ts.map +1 -1
  101. package/dist/permissions/modePolicy.js +40 -17
  102. package/dist/permissions/rules.d.ts.map +1 -1
  103. package/dist/permissions/rules.js +7 -5
  104. package/dist/plugins/data.d.ts.map +1 -1
  105. package/dist/plugins/data.js +6 -2
  106. package/dist/plugins/index.d.ts.map +1 -1
  107. package/dist/plugins/index.js +18 -7
  108. package/dist/plugins/installer.d.ts.map +1 -1
  109. package/dist/plugins/installer.js +17 -10
  110. package/dist/plugins/sources.d.ts.map +1 -1
  111. package/dist/plugins/sources.js +9 -3
  112. package/dist/tools/executor.d.ts.map +1 -1
  113. package/dist/tools/executor.js +470 -173
  114. package/dist/tools/sandbox.d.ts.map +1 -1
  115. package/dist/tools/sandbox.js +5 -1
  116. package/dist/tools/symbols.d.ts.map +1 -1
  117. package/dist/tools/symbols.js +30 -8
  118. package/dist/tools/tsLangService.d.ts.map +1 -1
  119. package/dist/tools/tsLangService.js +12 -4
  120. package/dist/types.d.ts +17 -0
  121. package/dist/types.d.ts.map +1 -1
  122. package/dist/util/frontmatter.d.ts.map +1 -1
  123. package/dist/util/miniYaml.d.ts.map +1 -1
  124. package/dist/util/miniYaml.js +4 -2
  125. 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,14 +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
- 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');
686
784
  // Abort bridge: streamChat polls a plain {aborted} object, and a hook must never hang
687
785
  // the turn — the timer flips it at the hook's own timeout, the outer signal aborts too.
688
786
  const ctl = { aborted: false };
689
- 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));
690
790
  const outerPoll = opts.abortSignal
691
- ? setInterval(() => { if (opts.abortSignal?.aborted)
692
- ctl.aborted = true; }, 100)
791
+ ? setInterval(() => {
792
+ if (opts.abortSignal?.aborted)
793
+ ctl.aborted = true;
794
+ }, 100)
693
795
  : undefined;
694
796
  if (opts.abortSignal?.aborted) {
695
797
  clearTimeout(timer);
@@ -706,10 +808,17 @@ async function promptHook(hook, payload, opts) {
706
808
  clearInterval(outerPoll);
707
809
  }
708
810
  async function runModel() {
709
- const messages = [{
811
+ const messages = [
812
+ {
710
813
  role: 'user',
711
- content: [{ type: 'text', text: `${PROMPT_HOOK_RULES}\n\nHook input:\n${payload}\n\nHook instruction:\n${body}` }],
712
- }];
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
+ ];
713
822
  const isAgent = hook.type === 'agent';
714
823
  try {
715
824
  let replyText = '';
@@ -723,11 +832,18 @@ async function promptHook(hook, payload, opts) {
723
832
  // read-only tools and a prompt hook sees none (the marker matches nothing).
724
833
  toolAllowlist: isAgent ? HOOK_AGENT_TOOLS.map((t) => t.name) : [NO_TOOLS_MARKER],
725
834
  extraTools: isAgent ? HOOK_AGENT_TOOLS : undefined,
726
- }, () => { });
727
- 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
+ : [];
728
841
  const toolUses = blocks.filter((b) => b.type === 'tool_use');
729
842
  if (!toolUses.length || !isAgent) {
730
- 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');
731
847
  break;
732
848
  }
733
849
  // Run the read-only tool round and feed the results back.
@@ -738,7 +854,9 @@ async function promptHook(hook, payload, opts) {
738
854
  try {
739
855
  out = getHookToolExecutors().includes(String(tu.name))
740
856
  ? await (0, executor_1.executeTool)(String(tu.name), tu.input ?? {}, ctl, undefined, opts.cwd)
741
- : { 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
+ };
742
860
  }
743
861
  catch (err) {
744
862
  out = { error: err.message || 'tool failed' };
@@ -746,7 +864,11 @@ async function promptHook(hook, payload, opts) {
746
864
  results.push({
747
865
  type: 'tool_result',
748
866
  tool_use_id: String(tu.id),
749
- 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 ?? ''),
750
872
  is_error: out.error !== undefined,
751
873
  });
752
874
  }
@@ -762,7 +884,9 @@ async function promptHook(hook, payload, opts) {
762
884
  if (decided)
763
885
  return { status: 2, stdout: '', stderr: parsed.reason || `Blocked by ${hook.type} hook` };
764
886
  // Allow — carry additionalContext through the same JSON channel callers parse.
765
- const passthrough = parsed.additionalContext ? JSON.stringify({ additionalContext: parsed.additionalContext }) : '';
887
+ const passthrough = parsed.additionalContext
888
+ ? JSON.stringify({ additionalContext: parsed.additionalContext })
889
+ : '';
766
890
  return { status: 0, stdout: passthrough, stderr: '' };
767
891
  }
768
892
  catch (err) {
@@ -841,7 +965,11 @@ function hookTimeoutMs(hook, fallbackMs = 60000) {
841
965
  function hookMatches(matcher, toolName) {
842
966
  if (!matcher || matcher === '*')
843
967
  return true;
844
- 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) => {
845
973
  const norm = (0, agentTypes_1.normaliseToolName)(alt);
846
974
  return norm === toolName || (norm === alt && toolName.includes(alt));
847
975
  });
@@ -913,7 +1041,9 @@ async function runToolHooks(entries, phase, toolName, input, workDir, result, ho
913
1041
  outcome.context = (outcome.context ? outcome.context + '\n' : '') + j.additionalContext;
914
1042
  }
915
1043
  }
916
- catch { /* not a directive — ignore */ }
1044
+ catch {
1045
+ /* not a directive — ignore */
1046
+ }
917
1047
  }
918
1048
  // Exit code 2 → hard block; stderr is the reason fed back to the model
919
1049
  if (r.status === 2) {
@@ -940,10 +1070,23 @@ async function runSimpleHooks(defs, workDir, extraEnv, hookOpts, event = 'hook')
940
1070
  if (gate.skip)
941
1071
  continue;
942
1072
  if (gate.background) {
943
- 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
+ });
944
1081
  continue;
945
1082
  }
946
- 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
+ });
947
1090
  if (gate.statusId !== undefined)
948
1091
  hookStatusEnd(gate.statusId);
949
1092
  if (r.status === 0 && gate.key)
@@ -963,7 +1106,13 @@ function flattenHookDefs(items, matchValue) {
963
1106
  const m = it.matcher;
964
1107
  // For these events the matcher (if any) selects on a plain value (e.g. the compact
965
1108
  // trigger "auto"/"manual"); empty or "*" matches all.
966
- 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))
967
1116
  continue;
968
1117
  out.push(...it.hooks);
969
1118
  }
@@ -1012,19 +1161,30 @@ async function runLifecycleHooks(items, event, workDir, payload, matchValue, hoo
1012
1161
  continue;
1013
1162
  }
1014
1163
  const timeout = hookTimeoutMs(hook, hook.type === 'agent' ? 60000 : hook.type === 'prompt' ? 30000 : 30000);
1015
- 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
+ });
1016
1171
  const out = (r.stdout ?? '').toString().trim();
1017
1172
  if (out.startsWith('{')) {
1018
1173
  try {
1019
1174
  const j = JSON.parse(out);
1020
1175
  // PermissionRequest answers, in nex's flat form or Claude Code's hookSpecificOutput form.
1021
1176
  if (event === 'PermissionRequest') {
1022
- 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);
1023
1179
  if (d === 'allow' || d === 'approve')
1024
1180
  outcome.allow = true;
1025
1181
  else if (d === 'deny' || d === 'block') {
1026
1182
  outcome.block = true;
1027
- 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';
1028
1188
  }
1029
1189
  }
1030
1190
  if (j.decision === 'block') {
@@ -1039,7 +1199,9 @@ async function runLifecycleHooks(items, event, workDir, payload, matchValue, hoo
1039
1199
  if (typeof j.systemMessage === 'string' && j.systemMessage)
1040
1200
  outcome.systemMessage = j.systemMessage;
1041
1201
  }
1042
- catch { /* not a directive */ }
1202
+ catch {
1203
+ /* not a directive */
1204
+ }
1043
1205
  }
1044
1206
  else if (out && (event === 'SessionStart' || event === 'UserPromptSubmit') && r.status === 0) {
1045
1207
  // Claude Code parity: plain stdout from these two events is added as context.
@@ -1073,7 +1235,9 @@ async function runPermissionRequestHooks(workDir, tool, input, sessionId = '') {
1073
1235
  if (o.allow)
1074
1236
  return { answer: 'allow' };
1075
1237
  }
1076
- catch { /* a hook must never break the prompt */ }
1238
+ catch {
1239
+ /* a hook must never break the prompt */
1240
+ }
1077
1241
  return { answer: null };
1078
1242
  }
1079
1243
  /** `hookSpecificOutput:{action,content}` (Claude Code's shape) or the flat form; null = no answer. */
@@ -1109,7 +1273,13 @@ async function runElicitationHooks(workDir, server, input, sessionId = '') {
1109
1273
  const defs = flattenHookDefs(items, server);
1110
1274
  if (!defs.length)
1111
1275
  return null;
1112
- 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
+ });
1113
1283
  for (const hook of defs) {
1114
1284
  if (!isRunnableHook(hook))
1115
1285
  continue;
@@ -1117,10 +1287,22 @@ async function runElicitationHooks(workDir, server, input, sessionId = '') {
1117
1287
  if (gate.skip)
1118
1288
  continue;
1119
1289
  if (gate.background) {
1120
- 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
+ });
1121
1298
  continue;
1122
1299
  }
1123
- 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
+ });
1124
1306
  if (gate.statusId !== undefined)
1125
1307
  hookStatusEnd(gate.statusId);
1126
1308
  if (r.status === 0 && gate.key)
@@ -1132,7 +1314,9 @@ async function runElicitationHooks(workDir, server, input, sessionId = '') {
1132
1314
  return answer;
1133
1315
  }
1134
1316
  }
1135
- catch { /* a hook must never break elicitation */ }
1317
+ catch {
1318
+ /* a hook must never break elicitation */
1319
+ }
1136
1320
  return null;
1137
1321
  }
1138
1322
  /**
@@ -1156,14 +1340,32 @@ async function runElicitationResultHooks(workDir, server, answer, meta = {}) {
1156
1340
  if (gate.skip)
1157
1341
  continue;
1158
1342
  const payload = JSON.stringify({
1159
- session_id: meta.sessionId ?? '', cwd: workDir, hook_event_name: 'ElicitationResult', mcp_server_name: server,
1160
- 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,
1161
1351
  });
1162
1352
  if (gate.background) {
1163
- 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
+ });
1164
1361
  continue;
1165
1362
  }
1166
- 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
+ });
1167
1369
  if (gate.statusId !== undefined)
1168
1370
  hookStatusEnd(gate.statusId);
1169
1371
  if (r.status === 0 && gate.key)
@@ -1212,9 +1414,13 @@ function fireNotificationHooks(workDir, message, type = 'permission_prompt') {
1212
1414
  const items = loadHooks(workDir).Notification;
1213
1415
  if (!items?.length)
1214
1416
  return;
1215
- 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 */
1216
1423
  }
1217
- catch { /* ignore */ }
1218
1424
  }
1219
1425
  /** For hosts that run their own manual /compact: fire PreCompact (trigger "manual"), awaited, never blocking. */
1220
1426
  /** The manual /compact counterpart of the auto PostCompact hook (trigger "manual"). */
@@ -1225,7 +1431,9 @@ async function fireManualPostCompactHook(workDir, sessionId, messagesSummarized)
1225
1431
  return;
1226
1432
  await runLifecycleHooks(items, 'PostCompact', workDir, { trigger: 'manual', session_id: sessionId, messages_summarized: messagesSummarized }, 'manual');
1227
1433
  }
1228
- catch { /* a hook must never break compaction */ }
1434
+ catch {
1435
+ /* a hook must never break compaction */
1436
+ }
1229
1437
  }
1230
1438
  async function fireManualPreCompactHook(workDir, sessionId, messagesToSummarize) {
1231
1439
  try {
@@ -1234,6 +1442,8 @@ async function fireManualPreCompactHook(workDir, sessionId, messagesToSummarize)
1234
1442
  return;
1235
1443
  await runLifecycleHooks(items, 'PreCompact', workDir, { trigger: 'manual', session_id: sessionId, messages_to_summarize: messagesToSummarize }, 'manual');
1236
1444
  }
1237
- catch { /* a hook must never break compaction */ }
1445
+ catch {
1446
+ /* a hook must never break compaction */
1447
+ }
1238
1448
  }
1239
1449
  //# sourceMappingURL=hooks.js.map