@yemi33/minions 0.1.2383 → 0.1.2384

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.
@@ -14,7 +14,7 @@
14
14
 
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
- const { execSync: _execSync } = require('child_process');
17
+ const { execSync: _execSync, execFile: _execFile } = require('child_process');
18
18
 
19
19
  // Shared.js-local PID liveness check, moved here unchanged. Avoids a circular
20
20
  // require on engine/cli.js (which has its own isPidAlive) and engine/timeout.js
@@ -24,7 +24,12 @@ const { execSync: _execSync } = require('child_process');
24
24
  function isPidAlive(pid) {
25
25
  if (!Number.isFinite(pid) || pid <= 0) return false;
26
26
  try { process.kill(pid, 0); return true; }
27
- catch { return false; }
27
+ catch (err) {
28
+ // Signal 0 returning EPERM means the process exists but this caller cannot
29
+ // inspect it. Treating that as dead lets lock recovery delete a live
30
+ // holder's lock and breaks cross-process mutual exclusion.
31
+ return err && err.code === 'EPERM';
32
+ }
28
33
  }
29
34
 
30
35
  // ─── Cross-Platform Process Kill Helpers ─────────────────────────────────────
@@ -206,6 +211,7 @@ function _parseWmicCsv(text) {
206
211
  const ppidIdx = idx('ParentProcessId');
207
212
  const pidIdx = idx('ProcessId');
208
213
  const cmdIdx = idx('CommandLine');
214
+ const creationIdx = idx('CreationDate');
209
215
  if (nameIdx < 0 || ppidIdx < 0 || pidIdx < 0) return [];
210
216
  const out = [];
211
217
  for (let i = 1; i < lines.length; i++) {
@@ -218,28 +224,31 @@ function _parseWmicCsv(text) {
218
224
  const ppid = parseInt(cols[ppidIdx], 10);
219
225
  const name = cols[nameIdx] || '';
220
226
  const cmd = cmdIdx >= 0 ? cols.slice(cmdIdx).join(',') : '';
221
- out.push({ pid, ppid: Number.isInteger(ppid) ? ppid : 0, name, cmd });
227
+ const created = creationIdx >= 0
228
+ ? String(cols[creationIdx] || '').match(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d{3})\d{3}([+-]\d{3})$/)
229
+ : null;
230
+ const startedAt = created
231
+ ? Date.UTC(
232
+ Number(created[1]), Number(created[2]) - 1, Number(created[3]),
233
+ Number(created[4]), Number(created[5]), Number(created[6]), Number(created[7])
234
+ ) - Number(created[8]) * 60000
235
+ : 0;
236
+ out.push({ pid, ppid: Number.isInteger(ppid) ? ppid : 0, name, cmd, startedAt });
222
237
  }
223
238
  return out;
224
239
  }
225
240
 
241
+ const PROCESS_LIST_PS_SCRIPT = "Get-CimInstance Win32_Process | ForEach-Object { [PSCustomObject]@{ Name = $_.Name; ParentProcessId = $_.ParentProcessId; ProcessId = $_.ProcessId; CommandLine = $_.CommandLine; StartedAt = if ($_.CreationDate) { [int64](([datetimeoffset]$_.CreationDate).ToUnixTimeMilliseconds()) } else { 0 } } } | ConvertTo-Json -Compress -Depth 2";
242
+
226
243
  // PowerShell Get-CimInstance is the modern path (wmic is removed on Win11
227
244
  // 24H2+). We try it first and fall back to wmic for older Windows hosts.
228
245
  function _psListProcesses() {
229
- const script = "Get-CimInstance Win32_Process | Select-Object Name,ParentProcessId,ProcessId,CommandLine | ConvertTo-Json -Compress -Depth 2";
230
246
  try {
231
247
  const out = _execSync(
232
- `powershell -NoProfile -NonInteractive -Command "${script}"`,
248
+ `powershell -NoProfile -NonInteractive -Command "${PROCESS_LIST_PS_SCRIPT}"`,
233
249
  { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 4000, windowsHide: true, maxBuffer: 4 * 1024 * 1024 }
234
250
  );
235
- const parsed = JSON.parse(out);
236
- const arr = Array.isArray(parsed) ? parsed : [parsed];
237
- return arr.map(p => ({
238
- pid: Number(p.ProcessId),
239
- ppid: Number(p.ParentProcessId) || 0,
240
- name: p.Name || '',
241
- cmd: p.CommandLine || '',
242
- })).filter(p => Number.isInteger(p.pid) && p.pid > 0);
251
+ return _parsePowerShellProcesses(out);
243
252
  } catch { return null; }
244
253
  }
245
254
 
@@ -261,11 +270,7 @@ function _unixListProcesses() {
261
270
  'ps -A -o pid=,ppid=,comm=',
262
271
  { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 5000 }
263
272
  );
264
- return out.split(/\r?\n/).map(line => {
265
- const m = line.trim().match(/^(\d+)\s+(\d+)\s+(.*)$/);
266
- if (!m) return null;
267
- return { pid: parseInt(m[1], 10), ppid: parseInt(m[2], 10), name: m[3], cmd: '' };
268
- }).filter(Boolean);
273
+ return _parseUnixProcesses(out);
269
274
  } catch { return []; }
270
275
  }
271
276
 
@@ -273,6 +278,103 @@ function listAllProcesses() {
273
278
  return process.platform === 'win32' ? _winListProcesses() : _unixListProcesses();
274
279
  }
275
280
 
281
+ function _parsePowerShellProcesses(out) {
282
+ const parsed = JSON.parse(out);
283
+ const arr = Array.isArray(parsed) ? parsed : [parsed];
284
+ return arr.map(p => ({
285
+ pid: Number(p.ProcessId),
286
+ ppid: Number(p.ParentProcessId) || 0,
287
+ name: p.Name || '',
288
+ cmd: p.CommandLine || '',
289
+ startedAt: Number(p.StartedAt) || 0,
290
+ })).filter(p => Number.isInteger(p.pid) && p.pid > 0);
291
+ }
292
+
293
+ function _parseUnixProcesses(out) {
294
+ return out.split(/\r?\n/).map(line => {
295
+ const m = line.trim().match(/^(\d+)\s+(\d+)\s+(.*)$/);
296
+ if (!m) return null;
297
+ return { pid: parseInt(m[1], 10), ppid: parseInt(m[2], 10), name: m[3], cmd: '' };
298
+ }).filter(Boolean);
299
+ }
300
+
301
+ function _parseUnixProcessesWithStart(out) {
302
+ return out.split(/\r?\n/).map(line => {
303
+ const m = line.trim().match(/^(\d+)\s+(\d+)\s+(.{24})\s+(.*)$/);
304
+ if (!m) return null;
305
+ const startedAt = Date.parse(m[3].trim());
306
+ return {
307
+ pid: parseInt(m[1], 10),
308
+ ppid: parseInt(m[2], 10),
309
+ name: m[4],
310
+ cmd: '',
311
+ startedAt: Number.isNaN(startedAt) ? 0 : startedAt,
312
+ };
313
+ }).filter(Boolean);
314
+ }
315
+
316
+ async function _applyLinuxStartTicks(processes) {
317
+ if (process.platform !== 'linux') return processes;
318
+ await Promise.all(processes.map(async (entry) => {
319
+ try {
320
+ const stat = await fs.promises.readFile(`/proc/${entry.pid}/stat`, 'utf8');
321
+ const fields = stat.slice(stat.lastIndexOf(') ') + 2).trim().split(/\s+/);
322
+ const startTicks = Number(fields[19]);
323
+ entry.startedAt = Number.isFinite(startTicks) && startTicks > 0 ? startTicks : 0;
324
+ } catch {
325
+ entry.startedAt = 0;
326
+ }
327
+ }));
328
+ return processes;
329
+ }
330
+
331
+ function _execFileText(file, args, opts) {
332
+ return new Promise((resolve, reject) => {
333
+ _execFile(file, args, opts, (err, stdout) => {
334
+ if (err) reject(err);
335
+ else resolve(stdout);
336
+ });
337
+ });
338
+ }
339
+
340
+ async function listAllProcessesAsync() {
341
+ if (process.platform === 'win32') {
342
+ try {
343
+ const out = await _execFileText(
344
+ 'powershell',
345
+ ['-NoProfile', '-NonInteractive', '-Command', PROCESS_LIST_PS_SCRIPT],
346
+ { encoding: 'utf8', windowsHide: true, timeout: 4000, maxBuffer: 4 * 1024 * 1024 }
347
+ );
348
+ const parsed = _parsePowerShellProcesses(out);
349
+ if (parsed.length > 0) return parsed;
350
+ } catch { /* fall through to wmic */ }
351
+ try {
352
+ const out = await _execFileText(
353
+ 'wmic',
354
+ ['process', 'get', 'CreationDate,Name,ParentProcessId,ProcessId', '/format:csv'],
355
+ { encoding: 'utf8', windowsHide: true, timeout: 4000, maxBuffer: 4 * 1024 * 1024 }
356
+ );
357
+ return _parseWmicCsv(out);
358
+ } catch {
359
+ return [];
360
+ }
361
+ }
362
+ try {
363
+ const out = await _execFileText(
364
+ 'ps',
365
+ ['-A', '-o', 'pid=,ppid=,lstart=,comm='],
366
+ {
367
+ encoding: 'utf8',
368
+ timeout: 5000,
369
+ env: { ...process.env, LC_ALL: 'C', LANG: 'C' },
370
+ }
371
+ );
372
+ return await _applyLinuxStartTicks(_parseUnixProcessesWithStart(out));
373
+ } catch {
374
+ return [];
375
+ }
376
+ }
377
+
276
378
  // W-mq6f2fe0000557fa — orphan-worktree GC: identify OS processes whose cwd
277
379
  // is at or inside `dir`. Cross-platform with a fail-open contract: on any
278
380
  // error or timeout we return [] (the caller continues with its existing
@@ -315,6 +417,7 @@ function findProcessesWithCwdInside(dir, opts = {}) {
315
417
  if (process.platform === 'win32') {
316
418
  return _findWindowsProcessesWithCwdInside(resolved, timeoutMs, now);
317
419
  }
420
+
318
421
  if (process.platform === 'linux') {
319
422
  return _findLinuxProcessesWithCwdInside(resolved, timeoutMs, now);
320
423
  }
@@ -322,6 +425,37 @@ function findProcessesWithCwdInside(dir, opts = {}) {
322
425
  } catch { return []; }
323
426
  }
324
427
 
428
+ async function findProcessesWithCwdInsideAsync(dir, opts = {}) {
429
+ const timeoutMs = Number(opts.timeoutMs) > 0 ? Number(opts.timeoutMs) : 5000;
430
+ const script = [
431
+ 'const utils = require(process.argv[1]);',
432
+ '(async () => {',
433
+ 'const timeoutMs = Number(process.argv[3]);',
434
+ "const rows = process.platform === 'win32'",
435
+ '? utils.findProcessCwdHolders(process.argv[2], { timeoutMs })',
436
+ ': utils.findProcessesWithCwdInside(process.argv[2], { timeoutMs });',
437
+ 'const processes = await utils.listAllProcessesAsync();',
438
+ 'const byPid = new Map(processes.map((entry) => [Number(entry.pid), entry]));',
439
+ 'const enriched = rows.map((entry) => ({',
440
+ '...entry, startedAt: Number(byPid.get(Number(entry.pid))?.startedAt) || 0,',
441
+ '})).filter((entry) => entry.startedAt > 0);',
442
+ 'process.stdout.write(JSON.stringify(enriched));',
443
+ '})().catch((err) => { console.error(err.message); process.exitCode = 1; });',
444
+ ].join('');
445
+ const out = await _execFileText(
446
+ process.execPath,
447
+ ['-e', script, __filename, String(dir || ''), String(timeoutMs)],
448
+ {
449
+ encoding: 'utf8',
450
+ timeout: timeoutMs + 2000,
451
+ windowsHide: true,
452
+ maxBuffer: 4 * 1024 * 1024,
453
+ }
454
+ );
455
+ const parsed = JSON.parse(out || '[]');
456
+ return Array.isArray(parsed) ? parsed : [];
457
+ }
458
+
325
459
  function _findWindowsProcessesWithCwdInside(resolved, timeoutMs, now) {
326
460
  // Win32_Process exposes CommandLine but not the cwd. Match by basename of
327
461
  // the worktree dir (the dispatch id like W-mq1k8z6o003acd89) appearing in
@@ -695,7 +829,9 @@ module.exports = {
695
829
  _winListProcesses,
696
830
  _unixListProcesses,
697
831
  listAllProcesses,
832
+ listAllProcessesAsync,
698
833
  findProcessesWithCwdInside,
834
+ findProcessesWithCwdInsideAsync,
699
835
  _findWindowsProcessesWithCwdInside,
700
836
  _findLinuxProcessesWithCwdInside,
701
837
  _findMacProcessesWithCwdInside,
@@ -32,7 +32,7 @@ const https = require('https');
32
32
  const os = require('os');
33
33
  const path = require('path');
34
34
  const { execSync } = require('child_process');
35
- const { FAILURE_CLASS, safeWrite, ts, resolveEngineCacheDir } = require('../shared');
35
+ const { ENGINE_DEFAULTS, FAILURE_CLASS, safeWrite, ts, resolveEngineCacheDir } = require('../shared');
36
36
 
37
37
  const ENGINE_DIR = __dirname.replace(/[\\/]runtimes$/, '');
38
38
  const _CACHE_DIR = resolveEngineCacheDir(ENGINE_DIR);
@@ -568,7 +568,8 @@ function resolveInvocationOptions({
568
568
  const resolved = { ...options };
569
569
  if (resolved.stream == null) resolved.stream = engineConfig.copilotStreamMode;
570
570
  if (resolved.disableBuiltinMcps == null) {
571
- resolved.disableBuiltinMcps = engineConfig.copilotDisableBuiltinMcps;
571
+ resolved.disableBuiltinMcps = engineConfig.copilotDisableBuiltinMcps
572
+ ?? ENGINE_DEFAULTS.copilotDisableBuiltinMcps;
572
573
  }
573
574
  if (resolved.reasoningSummaries == null) {
574
575
  resolved.reasoningSummaries = engineConfig.copilotReasoningSummaries;
@@ -576,6 +577,11 @@ function resolveInvocationOptions({
576
577
  if (failureClass === FAILURE_CLASS.MODEL_UNAVAILABLE && engineConfig.copilotFallbackModel) {
577
578
  resolved.model = engineConfig.copilotFallbackModel;
578
579
  }
580
+ if (Array.isArray(resolved.disabledMcpServers)) {
581
+ resolved.disabledMcpServers = [...new Set(
582
+ resolved.disabledMcpServers.filter(Boolean).map(String)
583
+ )].sort();
584
+ }
579
585
  return resolved;
580
586
  }
581
587
 
@@ -588,8 +594,20 @@ function shouldSuppressPostMutationError({ mutationApplied, error } = {}) {
588
594
  return error.errorClass === 'unknown-model' || error.errorClass === 'model-unavailable';
589
595
  }
590
596
 
591
- function buildWorkerArgs() {
592
- return ['--acp', '--allow-all', '--max-autopilot-continues', '1'];
597
+ function buildWorkerArgs(options = {}) {
598
+ const maxTurns = Number(options.maxTurns);
599
+ const args = [
600
+ '--acp',
601
+ '--allow-all',
602
+ '--max-autopilot-continues',
603
+ String(Number.isFinite(maxTurns) && maxTurns > 0 ? Math.floor(maxTurns) : 1),
604
+ ];
605
+ if (options.disableBuiltinMcps === true) args.push('--disable-builtin-mcps');
606
+ if (options.reasoningSummaries === true) args.push('--enable-reasoning-summaries');
607
+ for (const name of options.disabledMcpServers || []) {
608
+ if (name) args.push('--disable-mcp-server', String(name));
609
+ }
610
+ return args;
593
611
  }
594
612
 
595
613
  function encodePooledOutput(event = {}) {