@yemi33/minions 0.1.1079 → 0.1.1081

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1081 (2026-04-18)
4
+
5
+ ### Features
6
+ - seed realActivityMap at spawn time, stamp pid in live-output (#1200)
7
+
8
+ ### Fixes
9
+ - avoid no-op work item writes
10
+ - resilient claude binary resolution + surface spawn errors
11
+ - resolve native claude.exe from npm wrapper on Windows
12
+ - cap temp-agent creation at maxConcurrent per tick (#1219)
13
+ - reassign pending items from unspawned temp agents to idle named agents (#1204) (#1212)
14
+ - guard undefined agent in pending dispatch loop (closes #1206) (#1210)
15
+ - improve fallback meeting conclusion
16
+ - remove command center chevron
17
+ - stamp live-output.log stub before spawn (#1198)
18
+ - harden settings save and migrate pr poll config
19
+
20
+ ### Other
21
+ - Use PAT for publish merges
22
+ - test(queries): add unit tests for invalidateDispatchCache/getInbox/getAgentCharter (#1214)
23
+ - test(shared): add unit tests for truncateTextBytes/tailTextBytes/execSilent/trackReviewMetric/parseCanonicalPrId (#1215)
24
+ - Fix publish workflow merge
25
+ - chore: raise default meeting round timeout
26
+ - Harden prompt context handling
27
+ - Harden loop watch conversion
28
+ - Add watches sidebar activity badge
29
+ - test(cli): add unit tests for handleCommand, start, stop, kill, spawn (#1191)
30
+ - chore: untrack pipeline files — local config only
31
+ - restore: recover daily-arch-improvement and weekly-dead-code-cleanup pipelines
32
+ - Harden CC stream resilience
33
+
3
34
  ## 0.1.1079 (2026-04-18)
4
35
 
5
36
  ### Features
package/engine/llm.js CHANGED
@@ -246,6 +246,7 @@ function callLLM(promptText, sysPromptText, { timeout = 120000, label = 'llm', m
246
246
  proc.on('error', (err) => {
247
247
  clearTimeout(timer);
248
248
  for (const f of cleanupFiles) safeUnlink(f);
249
+ console.error(`[LLM] spawn error (${label}): ${err.message}`);
249
250
  resolve({ text: '', usage: null, sessionId: null, code: 1, stderr: err.message, raw: '', toolUses: [] });
250
251
  });
251
252
  });
@@ -307,6 +308,7 @@ function callLLMStreaming(promptText, sysPromptText, { timeout = 120000, label =
307
308
  proc.on('error', (err) => {
308
309
  clearTimeout(timer);
309
310
  for (const f of cleanupFiles) safeUnlink(f);
311
+ console.error(`[LLM-stream] spawn error (${label}): ${err.message}`);
310
312
  resolve({ text: '', usage: null, sessionId: null, code: 1, stderr: err.message, raw: '', toolUses: [] });
311
313
  });
312
314
  });
package/engine/shared.js CHANGED
@@ -347,23 +347,30 @@ function withFileLock(lockPath, fn, {
347
347
  function mutateJsonFileLocked(filePath, mutateFn, {
348
348
  defaultValue = {},
349
349
  lockRetries,
350
- lockRetryBackoffMs
350
+ lockRetryBackoffMs,
351
+ skipWriteIfUnchanged = false
351
352
  } = {}) {
352
353
  const lockPath = `${filePath}.lock`;
353
354
  const retries = lockRetries ?? ENGINE_DEFAULTS.lockRetries;
354
355
  const retryBackoffMs = lockRetryBackoffMs ?? ENGINE_DEFAULTS.lockRetryBackoffMs;
355
356
  return withFileLock(lockPath, () => {
357
+ const fileExists = fs.existsSync(filePath);
356
358
  let data = safeJson(filePath);
359
+ const parsedInvalid = fileExists && data === null;
357
360
  if (data === null || typeof data !== 'object') data = Array.isArray(defaultValue) ? [...defaultValue] : { ...defaultValue };
358
- // Back up last-known-good state before mutation (best-effort)
359
- const backupPath = filePath + '.backup';
360
- try { if (fs.existsSync(filePath)) fs.copyFileSync(filePath, backupPath); } catch { /* backup is best-effort */ }
361
+ const beforeSerialized = skipWriteIfUnchanged ? JSON.stringify(data) : null;
361
362
  if (path.basename(filePath) === 'pull-requests.json' && Array.isArray(data)) {
362
363
  normalizePrRecords(data, resolveProjectForPrPath(filePath));
363
364
  }
364
365
  const next = mutateFn(data);
365
366
  const finalData = next === undefined ? data : next;
366
- safeWrite(filePath, finalData);
367
+ const shouldWrite = !skipWriteIfUnchanged || parsedInvalid || JSON.stringify(finalData) !== beforeSerialized;
368
+ if (shouldWrite) {
369
+ // Back up last-known-good state before mutation (best-effort)
370
+ const backupPath = filePath + '.backup';
371
+ try { if (fileExists) fs.copyFileSync(filePath, backupPath); } catch { /* backup is best-effort */ }
372
+ safeWrite(filePath, finalData);
373
+ }
367
374
  return finalData;
368
375
  }, { retries, retryBackoffMs });
369
376
  }
@@ -1351,7 +1358,7 @@ function mutateWorkItems(filePath, mutator) {
1351
1358
  return mutateJsonFileLocked(filePath, (data) => {
1352
1359
  if (!Array.isArray(data)) data = [];
1353
1360
  return mutator(data) || data;
1354
- }, { defaultValue: [] });
1361
+ }, { defaultValue: [], skipWriteIfUnchanged: true });
1355
1362
  }
1356
1363
 
1357
1364
  /**
@@ -36,55 +36,59 @@ if (caps?.claudeBin && fs.existsSync(caps.claudeBin)) {
36
36
  _cacheHit = true;
37
37
  }
38
38
 
39
- // Strategy 1: Check if `claude` is on PATH (native installer or npm global bin)
39
+ // Strategy 1: Find `claude` on PATH, then resolve to the actual binary
40
+ // Don't parse wrapper scripts — probe known binary locations relative to the wrapper's directory.
40
41
  if (!claudeBin) try {
41
42
  const isWin = process.platform === 'win32';
42
43
  const cmd = isWin ? 'where claude 2>NUL' : 'which claude 2>/dev/null';
43
44
  const which = exec(cmd, { encoding: 'utf8', env, timeout: 10000 }).trim().split('\n')[0].trim();
44
45
  if (which) {
45
46
  const whichNative = isWin ? which : which.replace(/^\/([a-zA-Z])\//, (_, d) => d.toUpperCase() + ':/').replace(/\//g, path.sep);
46
- // Check if it's a node wrapper (npm install) or native binary
47
- try {
48
- const content = fs.readFileSync(whichNative, 'utf8');
49
- // npm wrapper scripts reference cli.js extract the path
50
- const m = content.match(/node_modules[\\/]@anthropic-ai[\\/]claude-code[\\/]cli\.js/);
51
- if (m) {
52
- const candidate = path.join(path.dirname(whichNative), 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js');
53
- if (fs.existsSync(candidate)) { claudeBin = candidate; }
54
- }
55
- // npm wrapper may reference native binary (claude.exe) instead of cli.js
56
- if (!claudeBin) {
57
- const exeMatch = content.match(/node_modules[\\/]@anthropic-ai[\\/]claude-code[\\/]bin[\\/]claude(?:\.exe)?/);
58
- if (exeMatch) {
59
- const candidate = path.join(path.dirname(whichNative), 'node_modules', '@anthropic-ai', 'claude-code', 'bin', isWin ? 'claude.exe' : 'claude');
60
- if (fs.existsSync(candidate)) { claudeBin = candidate; claudeIsNative = true; }
61
- }
62
- }
63
- } catch {
64
- // Can't read as text — it's a compiled binary
65
- }
66
- if (!claudeBin) {
67
- // Native binary or wrapper without cli.js reference — use directly
68
- claudeBin = whichNative;
47
+ const baseDir = path.dirname(whichNative);
48
+ const ccPkg = path.join(baseDir, 'node_modules', '@anthropic-ai', 'claude-code');
49
+
50
+ // Probe in priority order: native binary > cli.js > wrapper itself
51
+ const nativeBin = path.join(ccPkg, 'bin', isWin ? 'claude.exe' : 'claude');
52
+ const cliJs = path.join(ccPkg, 'cli.js');
53
+
54
+ if (fs.existsSync(nativeBin)) {
55
+ claudeBin = nativeBin;
69
56
  claudeIsNative = true;
57
+ } else if (fs.existsSync(cliJs)) {
58
+ claudeBin = cliJs;
59
+ } else {
60
+ // Not an npm wrapper — check if the path itself is a native binary
61
+ // On Windows, only trust .exe files; shell scripts can't be spawned directly
62
+ const ext = path.extname(whichNative).toLowerCase();
63
+ if (!isWin || ext === '.exe') {
64
+ claudeBin = whichNative;
65
+ claudeIsNative = true;
66
+ }
70
67
  }
71
68
  }
72
69
  } catch { /* optional */ }
73
70
 
74
71
  // Strategy 2: Known node_modules locations (npm global installs)
72
+ // Check for native binary first, then cli.js
75
73
  if (!claudeBin) {
76
- const searchPaths = [
77
- process.env.npm_config_prefix ? path.join(process.env.npm_config_prefix, 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js') : '',
78
- process.env.APPDATA ? path.join(process.env.APPDATA, 'npm', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js') : '',
79
- '/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js',
80
- '/usr/lib/node_modules/@anthropic-ai/claude-code/cli.js',
81
- '/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js',
82
- path.join(path.dirname(process.execPath), '..', 'lib', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
83
- path.join(path.dirname(process.execPath), 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
84
- path.join(__dirname, '..', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
74
+ const isWin = process.platform === 'win32';
75
+ const prefixes = [
76
+ process.env.npm_config_prefix ? path.join(process.env.npm_config_prefix, 'node_modules', '@anthropic-ai', 'claude-code') : '',
77
+ process.env.APPDATA ? path.join(process.env.APPDATA, 'npm', 'node_modules', '@anthropic-ai', 'claude-code') : '',
78
+ '/usr/local/lib/node_modules/@anthropic-ai/claude-code',
79
+ '/usr/lib/node_modules/@anthropic-ai/claude-code',
80
+ '/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code',
81
+ path.join(path.dirname(process.execPath), '..', 'lib', 'node_modules', '@anthropic-ai', 'claude-code'),
82
+ path.join(path.dirname(process.execPath), 'node_modules', '@anthropic-ai', 'claude-code'),
83
+ path.join(__dirname, '..', 'node_modules', '@anthropic-ai', 'claude-code'),
85
84
  ].filter(Boolean);
86
- for (const p of searchPaths) {
87
- try { if (fs.existsSync(p)) { claudeBin = p; break; } } catch {}
85
+ for (const pkg of prefixes) {
86
+ try {
87
+ const nativeBin = path.join(pkg, 'bin', isWin ? 'claude.exe' : 'claude');
88
+ if (fs.existsSync(nativeBin)) { claudeBin = nativeBin; claudeIsNative = true; break; }
89
+ const cliJs = path.join(pkg, 'cli.js');
90
+ if (fs.existsSync(cliJs)) { claudeBin = cliJs; break; }
91
+ } catch {}
88
92
  }
89
93
  }
90
94
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1079",
3
+ "version": "0.1.1081",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "minions": "bin/minions.js"