@yemi33/minions 0.1.1080 → 0.1.1082

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,11 +1,12 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1080 (2026-04-18)
3
+ ## 0.1.1082 (2026-04-18)
4
4
 
5
5
  ### Features
6
6
  - seed realActivityMap at spawn time, stamp pid in live-output (#1200)
7
7
 
8
8
  ### Fixes
9
+ - avoid no-op work item writes
9
10
  - resilient claude binary resolution + surface spawn errors
10
11
  - resolve native claude.exe from npm wrapper on Windows
11
12
  - cap temp-agent creation at maxConcurrent per tick (#1219)
@@ -16,6 +17,37 @@
16
17
  - stamp live-output.log stub before spawn (#1198)
17
18
  - harden settings save and migrate pr poll config
18
19
 
20
+ ### Other
21
+ - refactor: extract _probeClaudePackage helper, use shared.log for spawn errors
22
+ - Make work item descriptions scrollable
23
+ - Use PAT for publish merges
24
+ - test(queries): add unit tests for invalidateDispatchCache/getInbox/getAgentCharter (#1214)
25
+ - test(shared): add unit tests for truncateTextBytes/tailTextBytes/execSilent/trackReviewMetric/parseCanonicalPrId (#1215)
26
+ - Fix publish workflow merge
27
+ - chore: raise default meeting round timeout
28
+ - Harden prompt context handling
29
+ - Harden loop watch conversion
30
+ - Add watches sidebar activity badge
31
+ - test(cli): add unit tests for handleCommand, start, stop, kill, spawn (#1191)
32
+ - chore: untrack pipeline files — local config only
33
+ - restore: recover daily-arch-improvement and weekly-dead-code-cleanup pipelines
34
+ - Harden CC stream resilience
35
+
36
+ ## 0.1.1079 (2026-04-18)
37
+
38
+ ### Features
39
+ - seed realActivityMap at spawn time, stamp pid in live-output (#1200)
40
+
41
+ ### Fixes
42
+ - resolve native claude.exe from npm wrapper on Windows
43
+ - cap temp-agent creation at maxConcurrent per tick (#1219)
44
+ - reassign pending items from unspawned temp agents to idle named agents (#1204) (#1212)
45
+ - guard undefined agent in pending dispatch loop (closes #1206) (#1210)
46
+ - improve fallback meeting conclusion
47
+ - remove command center chevron
48
+ - stamp live-output.log stub before spawn (#1198)
49
+ - harden settings save and migrate pr poll config
50
+
19
51
  ### Other
20
52
  - Use PAT for publish merges
21
53
  - test(queries): add unit tests for invalidateDispatchCache/getInbox/getAgentCharter (#1214)
@@ -453,7 +453,7 @@ function openWorkItemDetail(id) {
453
453
  '<span class="dispatch-type ' + (item.type || 'implement') + '">' + escHtml(item.type || 'implement') + '</span>' +
454
454
  '<span class="prd-item-priority ' + (item.priority || '') + '">' + escHtml(item.priority || 'medium') + '</span>' +
455
455
  '</div>';
456
- html += field('Description', '<div style="font-size:12px">' + renderMd((item.description || item.title || '—').slice(0, 1000)) + '</div>');
456
+ html += field('Description', '<div style="font-size:12px;max-height:320px;overflow-y:auto;padding:8px 10px;background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm)">' + renderMd(item.description || item.title || '—') + '</div>');
457
457
  html += field('Agent', escHtml(item.dispatched_to || item.agent || 'Auto'));
458
458
  html += field('Source', escHtml(item._source || 'central'));
459
459
  if (item.created) html += field('Created', escHtml(new Date(item.created).toLocaleString()));
package/engine/llm.js CHANGED
@@ -246,7 +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
+ shared.log('error', `LLM spawn error (${label}): ${err.message}`);
250
250
  resolve({ text: '', usage: null, sessionId: null, code: 1, stderr: err.message, raw: '', toolUses: [] });
251
251
  });
252
252
  });
@@ -308,7 +308,7 @@ function callLLMStreaming(promptText, sysPromptText, { timeout = 120000, label =
308
308
  proc.on('error', (err) => {
309
309
  clearTimeout(timer);
310
310
  for (const f of cleanupFiles) safeUnlink(f);
311
- console.error(`[LLM-stream] spawn error (${label}): ${err.message}`);
311
+ shared.log('error', `LLM-stream spawn error (${label}): ${err.message}`);
312
312
  resolve({ text: '', usage: null, sessionId: null, code: 1, stderr: err.message, raw: '', toolUses: [] });
313
313
  });
314
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
  /**
@@ -27,6 +27,16 @@ let claudeBin;
27
27
  let claudeIsNative = false; // true = native binary, false = node cli.js
28
28
  const capsCachePath = path.join(__dirname, 'claude-caps.json');
29
29
  let _cacheHit = false;
30
+ const isWin = process.platform === 'win32';
31
+
32
+ // Probe a @anthropic-ai/claude-code package dir for the best binary: native exe > cli.js
33
+ function _probeClaudePackage(pkgDir) {
34
+ const nativeBin = path.join(pkgDir, 'bin', isWin ? 'claude.exe' : 'claude');
35
+ if (fs.existsSync(nativeBin)) return { bin: nativeBin, native: true };
36
+ const cliJs = path.join(pkgDir, 'cli.js');
37
+ if (fs.existsSync(cliJs)) return { bin: cliJs, native: false };
38
+ return null;
39
+ }
30
40
 
31
41
  // Fast path: use cached binary path if it still exists on disk
32
42
  const caps = safeJson(capsCachePath);
@@ -36,31 +46,21 @@ if (caps?.claudeBin && fs.existsSync(caps.claudeBin)) {
36
46
  _cacheHit = true;
37
47
  }
38
48
 
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.
49
+ // Strategy 1: Find `claude` on PATH, then probe known binary locations relative to the wrapper's directory
41
50
  if (!claudeBin) try {
42
- const isWin = process.platform === 'win32';
43
51
  const cmd = isWin ? 'where claude 2>NUL' : 'which claude 2>/dev/null';
44
52
  const which = exec(cmd, { encoding: 'utf8', env, timeout: 10000 }).trim().split('\n')[0].trim();
45
53
  if (which) {
54
+ // On non-Windows under Git Bash/MSYS, `which` returns POSIX paths (/c/Users/...) — normalize
46
55
  const whichNative = isWin ? which : which.replace(/^\/([a-zA-Z])\//, (_, d) => d.toUpperCase() + ':/').replace(/\//g, path.sep);
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;
56
- claudeIsNative = true;
57
- } else if (fs.existsSync(cliJs)) {
58
- claudeBin = cliJs;
56
+ const ccPkg = path.join(path.dirname(whichNative), 'node_modules', '@anthropic-ai', 'claude-code');
57
+ const found = _probeClaudePackage(ccPkg);
58
+ if (found) {
59
+ claudeBin = found.bin;
60
+ claudeIsNative = found.native;
59
61
  } 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') {
62
+ // Not an npm wrapper — on Windows, only trust .exe files (shell scripts can't be spawned directly)
63
+ if (!isWin || path.extname(whichNative).toLowerCase() === '.exe') {
64
64
  claudeBin = whichNative;
65
65
  claudeIsNative = true;
66
66
  }
@@ -69,9 +69,7 @@ if (!claudeBin) try {
69
69
  } catch { /* optional */ }
70
70
 
71
71
  // Strategy 2: Known node_modules locations (npm global installs)
72
- // Check for native binary first, then cli.js
73
72
  if (!claudeBin) {
74
- const isWin = process.platform === 'win32';
75
73
  const prefixes = [
76
74
  process.env.npm_config_prefix ? path.join(process.env.npm_config_prefix, 'node_modules', '@anthropic-ai', 'claude-code') : '',
77
75
  process.env.APPDATA ? path.join(process.env.APPDATA, 'npm', 'node_modules', '@anthropic-ai', 'claude-code') : '',
@@ -84,10 +82,8 @@ if (!claudeBin) {
84
82
  ].filter(Boolean);
85
83
  for (const pkg of prefixes) {
86
84
  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; }
85
+ const found = _probeClaudePackage(pkg);
86
+ if (found) { claudeBin = found.bin; claudeIsNative = found.native; break; }
91
87
  } catch {}
92
88
  }
93
89
  }
@@ -96,8 +92,8 @@ if (!claudeBin) {
96
92
  if (!claudeBin) {
97
93
  try {
98
94
  const globalRoot = exec('npm root -g', { encoding: 'utf8', env, timeout: 10000 }).trim();
99
- const candidate = path.join(globalRoot, '@anthropic-ai', 'claude-code', 'cli.js');
100
- if (fs.existsSync(candidate)) claudeBin = candidate;
95
+ const found = _probeClaudePackage(path.join(globalRoot, '@anthropic-ai', 'claude-code'));
96
+ if (found) { claudeBin = found.bin; claudeIsNative = found.native; }
101
97
  } catch { /* optional */ }
102
98
  }
103
99
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1080",
3
+ "version": "0.1.1082",
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"