@yemi33/minions 0.1.360 → 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 +3 -1
- package/dashboard/js/render-prd.js +14 -7
- package/dashboard/js/settings.js +1 -1
- package/dashboard.js +6 -6
- package/engine/shared.js +8 -2
- package/engine.js +6 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
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
|
|
|
9
10
|
### Fixes
|
|
11
|
+
- archived PRD picker uses file key instead of positional index
|
|
10
12
|
- check pull-requests.json for existing PRs before no-PR retry
|
|
11
13
|
- smart no-PR handling — distinguish MCP stall from intentional no-PR
|
|
12
14
|
|
|
@@ -148,8 +148,11 @@ function renderPrdProgress(prog) {
|
|
|
148
148
|
|
|
149
149
|
// Linked work item info
|
|
150
150
|
const wi = wiById[i.id];
|
|
151
|
+
const wiAgent = wi?.dispatched_to ? (agentData.find(a => a.id === wi.dispatched_to) || {}) : null;
|
|
151
152
|
const wiLabel = wi ? '<span style="font-size:9px;color:var(--muted);background:var(--surface);padding:1px 5px;border-radius:3px;border:1px solid var(--border)" title="Work item: ' + escHtml(wi.id) + '">' +
|
|
152
|
-
escHtml(wi.id) +
|
|
153
|
+
escHtml(wi.id) + '</span>' : '';
|
|
154
|
+
const agentLabel = wiAgent ? '<span style="font-size:9px;color:var(--muted)" title="' + escHtml(wiAgent.name || wi.dispatched_to) + '">' +
|
|
155
|
+
(wiAgent.emoji || '') + ' ' + escHtml(wiAgent.name || wi.dispatched_to) + '</span>' : '';
|
|
153
156
|
|
|
154
157
|
// Requeue button for failed items
|
|
155
158
|
const canRequeue = wi && (wi.status === 'failed' || i.status === 'failed');
|
|
@@ -170,6 +173,7 @@ function renderPrdProgress(prog) {
|
|
|
170
173
|
'<span class="prd-item-id">' + escHtml(i.id) + '</span>' +
|
|
171
174
|
'<span class="prd-item-name" title="' + escHtml(i.name) + '">' + escHtml(i.name) + '</span>' +
|
|
172
175
|
wiLabel +
|
|
176
|
+
agentLabel +
|
|
173
177
|
requeueBtn +
|
|
174
178
|
(projBadges ? '<span>' + projBadges + '</span>' : '') +
|
|
175
179
|
(prLinks ? '<span>' + prLinks + '</span>' : '') +
|
|
@@ -320,7 +324,9 @@ function renderPrdProgress(prog) {
|
|
|
320
324
|
const borderColor = statusColor(i.status);
|
|
321
325
|
const src = escHtml(i.source || '');
|
|
322
326
|
const iid = escHtml(i.id || '');
|
|
323
|
-
const
|
|
327
|
+
const agentId = wi[i.id]?.dispatched_to || '';
|
|
328
|
+
const agentInfo = agentId ? (agentData.find(a => a.id === agentId) || {}) : null;
|
|
329
|
+
const agentDisplay = agentInfo ? (agentInfo.emoji || '') + ' ' + escHtml(agentInfo.name || agentId) : (agentId ? escHtml(agentId) : '');
|
|
324
330
|
const deps = (i.depends_on || []).join(', ');
|
|
325
331
|
const wipAnim = i.status === 'dispatched' ? 'animation:prdWipPulse 2s infinite;' : '';
|
|
326
332
|
html += '<div onclick="prdItemEdit(\'' + src + '\',\'' + iid + '\')" ' +
|
|
@@ -333,7 +339,7 @@ function renderPrdProgress(prog) {
|
|
|
333
339
|
'<div style="color:var(--text);font-size:11px;line-height:1.3;margin-bottom:3px;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical">' + escHtml(i.name) + '</div>' +
|
|
334
340
|
'<div style="display:flex;gap:4px;flex-wrap:wrap;align-items:center">' +
|
|
335
341
|
'<span class="prd-item-priority ' + (i.priority || '') + '" style="font-size:8px;padding:1px 4px">' + escHtml(i.priority || '') + '</span>' +
|
|
336
|
-
(
|
|
342
|
+
(agentDisplay ? '<span style="font-size:8px;color:var(--muted)">' + agentDisplay + '</span>' : '') +
|
|
337
343
|
(function() {
|
|
338
344
|
const w = wi[i.id];
|
|
339
345
|
if (!w) return '';
|
|
@@ -484,7 +490,7 @@ function openArchivedPrdModal() {
|
|
|
484
490
|
const done = g.items.filter(it => it.status === 'done').length;
|
|
485
491
|
const failed = g.items.filter(it => it.status === 'failed').length;
|
|
486
492
|
const completed = g.completedAt ? new Date(g.completedAt).toLocaleDateString() : '';
|
|
487
|
-
return '<div class="plan-card" style="cursor:pointer;margin-bottom:8px" onclick="showArchivedPrdDetail(' +
|
|
493
|
+
return '<div class="plan-card" style="cursor:pointer;margin-bottom:8px" onclick="showArchivedPrdDetail(\'' + escHtml(g.file) + '\')">' +
|
|
488
494
|
'<div class="plan-card-title" style="font-size:13px">' + escHtml(g.summary || g.file) + '</div>' +
|
|
489
495
|
'<div class="plan-card-meta">' +
|
|
490
496
|
(g.project ? '<span>' + escHtml(g.project) + '</span>' : '') +
|
|
@@ -504,16 +510,17 @@ function openArchivedPrdModal() {
|
|
|
504
510
|
document.getElementById('modal').classList.add('open');
|
|
505
511
|
}
|
|
506
512
|
|
|
507
|
-
function showArchivedPrdDetail(
|
|
513
|
+
function showArchivedPrdDetail(idxOrFile) {
|
|
508
514
|
const groups = window._archivedPrdGroups || [];
|
|
515
|
+
const idx = typeof idxOrFile === 'string' ? groups.findIndex(g => g.file === idxOrFile) : idxOrFile;
|
|
509
516
|
const g = groups[idx];
|
|
510
517
|
if (!g) return;
|
|
511
518
|
|
|
512
519
|
// View mode toggle for archived
|
|
513
520
|
const isGraph = window._archivedPrdViewMode !== 'list';
|
|
514
521
|
const toggleHtml = '<div style="display:flex;gap:4px;margin-bottom:8px">' +
|
|
515
|
-
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;' + (isGraph ? 'background:var(--blue);color:#fff;border-color:var(--blue)' : '') + '" onclick="window._archivedPrdViewMode=\'graph\';showArchivedPrdDetail(' +
|
|
516
|
-
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;' + (!isGraph ? 'background:var(--blue);color:#fff;border-color:var(--blue)' : '') + '" onclick="window._archivedPrdViewMode=\'list\';showArchivedPrdDetail(' +
|
|
522
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;' + (isGraph ? 'background:var(--blue);color:#fff;border-color:var(--blue)' : '') + '" onclick="window._archivedPrdViewMode=\'graph\';showArchivedPrdDetail(\'' + escHtml(g.file) + '\')">Graph</button>' +
|
|
523
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;' + (!isGraph ? 'background:var(--blue);color:#fff;border-color:var(--blue)' : '') + '" onclick="window._archivedPrdViewMode=\'list\';showArchivedPrdDetail(\'' + escHtml(g.file) + '\')">List</button>' +
|
|
517
524
|
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green);margin-left:auto" onclick="triggerVerify(\'' + escHtml(g.file) + '\')">Trigger Verify</button>' +
|
|
518
525
|
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="planUnarchive(\'' + escHtml(g.file) + '\',this)">Unarchive</button>' +
|
|
519
526
|
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="openArchivedPrdModal()">Back</button>' +
|
package/dashboard/js/settings.js
CHANGED
|
@@ -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 ||
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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"
|