@yemi33/minions 0.1.361 → 0.1.362

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,8 +1,9 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.361 (2026-04-06)
3
+ ## 0.1.362 (2026-04-06)
4
4
 
5
5
  ### Features
6
+ - Null-safe safeJson wrappers + dashboard crash guards (#203)
6
7
  - pipeline plan stage uses LLM to generate structured plan from meeting
7
8
  - show 'Converting to PRD' status instead of 'In Progress' during plan conversion
8
9
 
@@ -25,7 +25,7 @@ async function openSettings() {
25
25
  '<h3 style="font-size:13px;color:var(--blue);margin-bottom:8px">Engine</h3>' +
26
26
  '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px">' +
27
27
  settingsField('Tick Interval', 'set-tickInterval', e.tickInterval || 60000, 'ms', 'How often the engine runs discovery + dispatch') +
28
- settingsField('Max Concurrent Agents', 'set-maxConcurrent', e.maxConcurrent || 3, '', 'Max agents working simultaneously') +
28
+ settingsField('Max Concurrent Agents', 'set-maxConcurrent', e.maxConcurrent || 5, '', 'Max agents working simultaneously') +
29
29
  settingsField('Consolidation Threshold', 'set-inboxConsolidateThreshold', e.inboxConsolidateThreshold || 5, 'notes', 'Inbox notes before auto-consolidation') +
30
30
  settingsField('Agent Timeout', 'set-agentTimeout', e.agentTimeout || 18000000, 'ms', 'Kill agent after this duration') +
31
31
  settingsField('Max Turns', 'set-maxTurns', e.maxTurns || 100, '', 'Claude CLI --max-turns per agent') +
package/dashboard.js CHANGED
@@ -14,7 +14,7 @@ const shared = require('./engine/shared');
14
14
  const queries = require('./engine/queries');
15
15
  const os = require('os');
16
16
 
17
- const { safeRead, safeReadDir, safeWrite, safeJson, safeUnlink, mutateJsonFileLocked, getProjects: _getProjects, DONE_STATUSES } = shared;
17
+ const { safeRead, safeReadDir, safeWrite, safeJson, safeJsonObj, safeJsonArr, safeUnlink, mutateJsonFileLocked, getProjects: _getProjects, DONE_STATUSES } = shared;
18
18
  const { getAgents, getAgentDetail, getPrdInfo, getWorkItems, getDispatchQueue,
19
19
  getSkills, getInbox, getNotesWithMeta, getPullRequests,
20
20
  getEngineLog, getMetrics, getKnowledgeBaseEntries, timeSince,
@@ -1206,7 +1206,7 @@ const server = http.createServer(async (req, res) => {
1206
1206
  const planPath = resolvePlanPath(body.source);
1207
1207
  if (!fs.existsSync(planPath)) return jsonReply(res, 404, { error: 'plan file not found' });
1208
1208
  // Pre-check: verify item exists before taking the lock
1209
- const preCheck = safeJson(planPath);
1209
+ const preCheck = safeJsonObj(planPath);
1210
1210
  const preItem = (preCheck.missing_features || []).find(f => f.id === body.itemId);
1211
1211
  if (!preItem) return jsonReply(res, 404, { error: 'item not found in plan' });
1212
1212
 
@@ -1258,7 +1258,7 @@ const server = http.createServer(async (req, res) => {
1258
1258
  if (!body.source || !body.itemId) return jsonReply(res, 400, { error: 'source and itemId required' });
1259
1259
  const planPath = resolvePlanPath(body.source);
1260
1260
  if (!fs.existsSync(planPath)) return jsonReply(res, 404, { error: 'plan file not found' });
1261
- const plan = safeJson(planPath);
1261
+ const plan = safeJsonObj(planPath);
1262
1262
  const idx = (plan.missing_features || []).findIndex(f => f.id === body.itemId);
1263
1263
  if (idx < 0) return jsonReply(res, 404, { error: 'item not found in plan' });
1264
1264
 
@@ -2048,7 +2048,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
2048
2048
  if (!body.source) return jsonReply(res, 400, { error: 'source required' });
2049
2049
  const planPath = resolvePlanPath(body.source);
2050
2050
  if (!fs.existsSync(planPath)) return jsonReply(res, 404, { error: 'plan file not found' });
2051
- const plan = safeJson(planPath);
2051
+ const plan = safeJsonObj(planPath);
2052
2052
  const planItems = plan.missing_features || [];
2053
2053
 
2054
2054
  let reset = 0, kept = 0, newCount = 0;
@@ -2127,7 +2127,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
2127
2127
  }
2128
2128
  for (const wiPath of wiPaths) {
2129
2129
  try {
2130
- const items = safeJson(wiPath);
2130
+ const items = safeJsonArr(wiPath);
2131
2131
  const filtered = items.filter(w => w.sourcePlan !== body.file);
2132
2132
  if (filtered.length < items.length) {
2133
2133
  cleaned += items.length - filtered.length;
@@ -2834,7 +2834,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
2834
2834
  if (!fs.existsSync(target)) return jsonReply(res, 400, { error: 'Directory not found: ' + target });
2835
2835
 
2836
2836
  const configPath = path.join(MINIONS_DIR, 'config.json');
2837
- const config = safeJson(configPath);
2837
+ const config = safeJsonObj(configPath);
2838
2838
  if (!config.projects) config.projects = [];
2839
2839
 
2840
2840
  // Check if already linked
package/engine/shared.js CHANGED
@@ -73,6 +73,12 @@ function safeJson(p) {
73
73
  }
74
74
  }
75
75
 
76
+ /** Null-safe safeJson wrapper — returns {} when file is missing/corrupt. */
77
+ function safeJsonObj(p) { return safeJson(p) || {}; }
78
+
79
+ /** Null-safe safeJson wrapper — returns [] when file is missing/corrupt. */
80
+ function safeJsonArr(p) { return safeJson(p) || []; }
81
+
76
82
  /**
77
83
  * Monotonic counter for generating unique temp file names within this process.
78
84
  * Assumes single-thread execution (no worker_threads). If worker_threads are
@@ -422,7 +428,7 @@ const DEFAULT_AGENTS = {
422
428
 
423
429
  const DEFAULT_CLAUDE = {
424
430
  binary: 'claude',
425
- outputFormat: 'json',
431
+ outputFormat: 'stream-json',
426
432
  allowedTools: 'Edit,Write,Read,Bash,Glob,Grep,Agent,WebFetch,WebSearch',
427
433
  };
428
434
 
@@ -623,7 +629,7 @@ module.exports = {
623
629
  log,
624
630
  safeRead,
625
631
  safeReadDir,
626
- safeJson,
632
+ safeJson, safeJsonObj, safeJsonArr,
627
633
  safeWrite,
628
634
  safeUnlink,
629
635
  withFileLock,
package/engine.js CHANGED
@@ -1416,7 +1416,12 @@ function discoverFromWorkItems(config, project) {
1416
1416
  // This protects against persisted state drift from old runtime versions.
1417
1417
  try {
1418
1418
  mutateDispatch((dp) => {
1419
- dp.completed = Array.isArray(dp.completed) ? dp.completed.filter(d => d.meta?.dispatchKey !== key) : [];
1419
+ const prev = Array.isArray(dp.completed) ? dp.completed : [];
1420
+ const next = [];
1421
+ for (let i = 0; i < prev.length; i++) {
1422
+ if (prev[i].meta?.dispatchKey !== key) next.push(prev[i]);
1423
+ }
1424
+ dp.completed = next;
1420
1425
  return dp;
1421
1426
  });
1422
1427
  dispatchCooldowns.delete(key);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.361",
3
+ "version": "0.1.362",
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"