@yemi33/minions 0.1.54 → 0.1.56
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 +14 -0
- package/bin/minions.js +63 -0
- package/dashboard/js/refresh.js +64 -45
- package/dashboard.js +36 -1
- package/engine/cli.js +40 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.56 (2026-03-30)
|
|
4
|
+
|
|
5
|
+
### Other
|
|
6
|
+
- bin/minions.js
|
|
7
|
+
|
|
8
|
+
## 0.1.55 (2026-03-30)
|
|
9
|
+
|
|
10
|
+
### Engine
|
|
11
|
+
- engine/cli.js
|
|
12
|
+
|
|
13
|
+
### Dashboard
|
|
14
|
+
- dashboard.js
|
|
15
|
+
- dashboard/js/refresh.js
|
|
16
|
+
|
|
3
17
|
## 0.1.54 (2026-03-30)
|
|
4
18
|
|
|
5
19
|
### Engine
|
package/bin/minions.js
CHANGED
|
@@ -472,6 +472,7 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
472
472
|
minions spawn <agent> <prompt> Manually spawn an agent
|
|
473
473
|
minions plan <file|text> [proj] Run a plan
|
|
474
474
|
minions cleanup Clean temp files, worktrees, zombies
|
|
475
|
+
minions reset --confirm Factory reset (delete all state, keep config)
|
|
475
476
|
|
|
476
477
|
Dashboard:
|
|
477
478
|
minions dash Start web dashboard (default :7331)
|
|
@@ -500,6 +501,68 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
500
501
|
dashProc.unref();
|
|
501
502
|
console.log(` Dashboard started (PID: ${dashProc.pid})`);
|
|
502
503
|
console.log(' Dashboard: http://localhost:7331\n');
|
|
504
|
+
} else if (cmd === 'reset') {
|
|
505
|
+
ensureInstalled();
|
|
506
|
+
if (!rest.includes('--confirm')) {
|
|
507
|
+
console.log(`
|
|
508
|
+
This will DELETE all runtime state:
|
|
509
|
+
- Work items, dispatch queue, PRDs, plans
|
|
510
|
+
- Agent history, sessions, output logs
|
|
511
|
+
- Notes, knowledge base, pinned notes
|
|
512
|
+
- Metrics, cooldowns, schedules
|
|
513
|
+
|
|
514
|
+
Config.json and agent charters are PRESERVED.
|
|
515
|
+
|
|
516
|
+
Run: minions reset --confirm
|
|
517
|
+
`);
|
|
518
|
+
process.exit(0);
|
|
519
|
+
}
|
|
520
|
+
// Stop engine + dashboard
|
|
521
|
+
try { execSync(`node "${path.join(MINIONS_HOME, 'engine.js')}" stop`, { stdio: 'ignore', cwd: MINIONS_HOME }); } catch {}
|
|
522
|
+
const glob = (dir, pattern) => { try { return fs.readdirSync(dir).filter(f => pattern.test(f)).map(f => path.join(dir, f)); } catch { return []; } };
|
|
523
|
+
const rm = (f) => { try { fs.unlinkSync(f); } catch {} };
|
|
524
|
+
const rmDir = (d) => { try { fs.rmSync(d, { recursive: true, force: true }); } catch {} };
|
|
525
|
+
const engineDir = path.join(MINIONS_HOME, 'engine');
|
|
526
|
+
// Engine state
|
|
527
|
+
for (const f of ['dispatch.json', 'control.json', 'log.json', 'metrics.json', 'cooldowns.json', 'schedule-runs.json', 'kb-checkpoint.json', 'cc-session.json', 'doc-sessions.json']) rm(path.join(engineDir, f));
|
|
528
|
+
glob(engineDir, /^pid-.*\.pid$/).forEach(rm);
|
|
529
|
+
rmDir(path.join(engineDir, 'tmp'));
|
|
530
|
+
// Work items + PRs
|
|
531
|
+
rm(path.join(MINIONS_HOME, 'work-items.json'));
|
|
532
|
+
rm(path.join(MINIONS_HOME, 'work-items-archive.json'));
|
|
533
|
+
rm(path.join(MINIONS_HOME, 'pull-requests.json'));
|
|
534
|
+
// Plans + PRDs
|
|
535
|
+
glob(path.join(MINIONS_HOME, 'plans'), /\.md$/).forEach(rm);
|
|
536
|
+
glob(path.join(MINIONS_HOME, 'prd'), /\.json$/).forEach(rm);
|
|
537
|
+
rmDir(path.join(MINIONS_HOME, 'prd', 'archive'));
|
|
538
|
+
rmDir(path.join(MINIONS_HOME, 'prd', 'guides'));
|
|
539
|
+
// Notes + KB
|
|
540
|
+
rm(path.join(MINIONS_HOME, 'notes.md'));
|
|
541
|
+
rm(path.join(MINIONS_HOME, 'pinned.md'));
|
|
542
|
+
rmDir(path.join(MINIONS_HOME, 'notes', 'inbox'));
|
|
543
|
+
rmDir(path.join(MINIONS_HOME, 'notes', 'archive'));
|
|
544
|
+
fs.mkdirSync(path.join(MINIONS_HOME, 'notes', 'inbox'), { recursive: true });
|
|
545
|
+
fs.mkdirSync(path.join(MINIONS_HOME, 'notes', 'archive'), { recursive: true });
|
|
546
|
+
for (const cat of ['architecture', 'conventions', 'project-notes', 'build-reports', 'reviews']) {
|
|
547
|
+
const catDir = path.join(MINIONS_HOME, 'knowledge', cat);
|
|
548
|
+
glob(catDir, /\.md$/).forEach(rm);
|
|
549
|
+
}
|
|
550
|
+
// Agent state (preserve charters)
|
|
551
|
+
const agentsDir = path.join(MINIONS_HOME, 'agents');
|
|
552
|
+
try {
|
|
553
|
+
for (const agent of fs.readdirSync(agentsDir)) {
|
|
554
|
+
const agentDir = path.join(agentsDir, agent);
|
|
555
|
+
if (!fs.statSync(agentDir).isDirectory()) continue;
|
|
556
|
+
for (const f of ['live-output.log', 'session.json', 'history.md', 'steer.md', 'status.json']) rm(path.join(agentDir, f));
|
|
557
|
+
glob(agentDir, /^output.*\.log$/).forEach(rm);
|
|
558
|
+
}
|
|
559
|
+
} catch {}
|
|
560
|
+
// Projects state
|
|
561
|
+
rmDir(path.join(MINIONS_HOME, 'projects'));
|
|
562
|
+
fs.mkdirSync(path.join(MINIONS_HOME, 'projects'), { recursive: true });
|
|
563
|
+
|
|
564
|
+
console.log('\n Reset complete. Config and charters preserved.');
|
|
565
|
+
console.log(' Run: minions up\n');
|
|
503
566
|
} else if (cmd === 'doctor') {
|
|
504
567
|
ensureInstalled();
|
|
505
568
|
const { doctor } = require(path.join(MINIONS_HOME, 'engine', 'preflight'));
|
package/dashboard/js/refresh.js
CHANGED
|
@@ -1,57 +1,76 @@
|
|
|
1
1
|
// refresh.js — Main refresh loop and initialization extracted from dashboard.html
|
|
2
2
|
|
|
3
|
+
function _processStatusUpdate(data) {
|
|
4
|
+
// Detect fresh install — clear stale browser state if install ID changed
|
|
5
|
+
if (data.installId) {
|
|
6
|
+
const prev = localStorage.getItem('minions-install-id');
|
|
7
|
+
if (prev && prev !== data.installId) {
|
|
8
|
+
localStorage.clear();
|
|
9
|
+
console.log('Minions: fresh install detected, cleared browser state');
|
|
10
|
+
}
|
|
11
|
+
localStorage.setItem('minions-install-id', data.installId);
|
|
12
|
+
}
|
|
13
|
+
document.getElementById('ts').textContent = new Date(data.timestamp).toLocaleTimeString();
|
|
14
|
+
const engineState = (data.engine && data.engine.state) ? data.engine.state : 'stopped';
|
|
15
|
+
document.getElementById('setup-banner').style.display = (!data.initialized && engineState !== 'stopped') ? 'block' : 'none';
|
|
16
|
+
renderAgents(data.agents);
|
|
17
|
+
renderPrdProgress(data.prdProgress);
|
|
18
|
+
_cachePrdItems(data.prdProgress);
|
|
19
|
+
renderInbox(data.inbox);
|
|
20
|
+
cmdUpdateAgentList(data.agents);
|
|
21
|
+
cmdUpdateProjectList(data.projects || []);
|
|
22
|
+
renderNotes(data.notes);
|
|
23
|
+
renderPrd(data.prd, data.prdProgress);
|
|
24
|
+
renderPrs(data.pullRequests || []);
|
|
25
|
+
renderArchiveButtons(data.archivedPrds || []);
|
|
26
|
+
renderEngineStatus(data.engine);
|
|
27
|
+
renderDispatch(data.dispatch);
|
|
28
|
+
window._lastDispatch = data.dispatch;
|
|
29
|
+
window._lastWorkItems = data.workItems || [];
|
|
30
|
+
window._lastStatus = data;
|
|
31
|
+
prunePrdRequeueState(window._lastWorkItems);
|
|
32
|
+
renderEngineLog(data.engineLog || []);
|
|
33
|
+
renderProjects(data.projects || []);
|
|
34
|
+
renderMetrics(data.metrics || {});
|
|
35
|
+
renderWorkItems(data.workItems || []);
|
|
36
|
+
renderSkills(data.skills || []);
|
|
37
|
+
renderMcpServers(data.mcpServers || []);
|
|
38
|
+
renderSchedules(data.schedules || []);
|
|
39
|
+
renderPinned(data.pinned || []);
|
|
40
|
+
// Update sidebar counts
|
|
41
|
+
const swi = document.getElementById('sidebar-wi');
|
|
42
|
+
if (swi) swi.textContent = (data.workItems || []).length || '';
|
|
43
|
+
const spr = document.getElementById('sidebar-pr');
|
|
44
|
+
if (spr) spr.textContent = (data.pullRequests || []).length || '';
|
|
45
|
+
// Refresh KB and plans less frequently (every 3rd cycle = ~12s)
|
|
46
|
+
if (!window._kbRefreshCount) window._kbRefreshCount = 0;
|
|
47
|
+
if (window._kbRefreshCount++ % 3 === 0) { refreshKnowledgeBase(); refreshPlans(); }
|
|
48
|
+
}
|
|
49
|
+
|
|
3
50
|
async function refresh() {
|
|
4
51
|
try {
|
|
5
52
|
const data = await fetch('/api/status').then(r => r.json());
|
|
6
|
-
|
|
7
|
-
if (data.installId) {
|
|
8
|
-
const prev = localStorage.getItem('minions-install-id');
|
|
9
|
-
if (prev && prev !== data.installId) {
|
|
10
|
-
localStorage.clear();
|
|
11
|
-
console.log('Minions: fresh install detected, cleared browser state');
|
|
12
|
-
}
|
|
13
|
-
localStorage.setItem('minions-install-id', data.installId);
|
|
14
|
-
}
|
|
15
|
-
document.getElementById('ts').textContent = new Date(data.timestamp).toLocaleTimeString();
|
|
16
|
-
const engineState = (data.engine && data.engine.state) ? data.engine.state : 'stopped';
|
|
17
|
-
document.getElementById('setup-banner').style.display = (!data.initialized && engineState !== 'stopped') ? 'block' : 'none';
|
|
18
|
-
renderAgents(data.agents);
|
|
19
|
-
renderPrdProgress(data.prdProgress);
|
|
20
|
-
_cachePrdItems(data.prdProgress);
|
|
21
|
-
renderInbox(data.inbox);
|
|
22
|
-
cmdUpdateAgentList(data.agents);
|
|
23
|
-
cmdUpdateProjectList(data.projects || []);
|
|
24
|
-
renderNotes(data.notes);
|
|
25
|
-
renderPrd(data.prd, data.prdProgress);
|
|
26
|
-
renderPrs(data.pullRequests || []);
|
|
27
|
-
renderArchiveButtons(data.archivedPrds || []);
|
|
28
|
-
renderEngineStatus(data.engine);
|
|
29
|
-
renderDispatch(data.dispatch);
|
|
30
|
-
window._lastDispatch = data.dispatch;
|
|
31
|
-
window._lastWorkItems = data.workItems || [];
|
|
32
|
-
window._lastStatus = data;
|
|
33
|
-
prunePrdRequeueState(window._lastWorkItems);
|
|
34
|
-
renderEngineLog(data.engineLog || []);
|
|
35
|
-
renderProjects(data.projects || []);
|
|
36
|
-
renderMetrics(data.metrics || {});
|
|
37
|
-
renderWorkItems(data.workItems || []);
|
|
38
|
-
renderSkills(data.skills || []);
|
|
39
|
-
renderMcpServers(data.mcpServers || []);
|
|
40
|
-
renderSchedules(data.schedules || []);
|
|
41
|
-
renderPinned(data.pinned || []);
|
|
42
|
-
// Update sidebar counts
|
|
43
|
-
const swi = document.getElementById('sidebar-wi');
|
|
44
|
-
if (swi) swi.textContent = (data.workItems || []).length || '';
|
|
45
|
-
const spr = document.getElementById('sidebar-pr');
|
|
46
|
-
if (spr) spr.textContent = (data.pullRequests || []).length || '';
|
|
47
|
-
// Refresh KB and plans less frequently (every 3rd cycle = ~12s)
|
|
48
|
-
if (!window._kbRefreshCount) window._kbRefreshCount = 0;
|
|
49
|
-
if (window._kbRefreshCount++ % 3 === 0) { refreshKnowledgeBase(); refreshPlans(); }
|
|
53
|
+
_processStatusUpdate(data);
|
|
50
54
|
} catch(e) { console.error('refresh error', e); }
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
refresh();
|
|
54
|
-
|
|
58
|
+
|
|
59
|
+
// SSE status stream — real-time push, falls back to 4s polling
|
|
60
|
+
let _statusStream = null;
|
|
61
|
+
try {
|
|
62
|
+
_statusStream = new EventSource('/api/status-stream');
|
|
63
|
+
_statusStream.onmessage = (e) => {
|
|
64
|
+
try { _processStatusUpdate(JSON.parse(e.data)); } catch {}
|
|
65
|
+
};
|
|
66
|
+
_statusStream.onerror = () => {
|
|
67
|
+
// Fall back to polling
|
|
68
|
+
if (_statusStream) { _statusStream.close(); _statusStream = null; }
|
|
69
|
+
setInterval(refresh, 4000);
|
|
70
|
+
};
|
|
71
|
+
} catch {
|
|
72
|
+
setInterval(refresh, 4000);
|
|
73
|
+
}
|
|
55
74
|
|
|
56
75
|
// Wire sidebar navigation
|
|
57
76
|
document.querySelectorAll('.sidebar-link').forEach(link => {
|
package/dashboard.js
CHANGED
|
@@ -180,7 +180,23 @@ function parsePinnedEntries(content) {
|
|
|
180
180
|
let _statusCache = null;
|
|
181
181
|
let _statusCacheTs = 0;
|
|
182
182
|
const STATUS_CACHE_TTL = 10000; // 10s — reduces expensive aggregation frequency; mutations call invalidateStatusCache()
|
|
183
|
-
|
|
183
|
+
const _statusStreamClients = new Set();
|
|
184
|
+
let _statusPushTimer = null;
|
|
185
|
+
let _lastStatusHash = '';
|
|
186
|
+
|
|
187
|
+
function invalidateStatusCache() {
|
|
188
|
+
_statusCache = null;
|
|
189
|
+
// Push to SSE clients (debounced 500ms to avoid flooding during batch mutations)
|
|
190
|
+
if (_statusPushTimer) return;
|
|
191
|
+
_statusPushTimer = setTimeout(() => {
|
|
192
|
+
_statusPushTimer = null;
|
|
193
|
+
if (_statusStreamClients.size === 0) return;
|
|
194
|
+
const data = JSON.stringify(getStatus());
|
|
195
|
+
for (const res of _statusStreamClients) {
|
|
196
|
+
try { res.write('data: ' + data + '\n\n'); } catch { _statusStreamClients.delete(res); }
|
|
197
|
+
}
|
|
198
|
+
}, 500);
|
|
199
|
+
}
|
|
184
200
|
|
|
185
201
|
function getStatus() {
|
|
186
202
|
const now = Date.now();
|
|
@@ -221,6 +237,19 @@ function getStatus() {
|
|
|
221
237
|
return _statusCache;
|
|
222
238
|
}
|
|
223
239
|
|
|
240
|
+
// Periodic push for engine-driven changes (dispatch.json, control.json) that bypass invalidateStatusCache
|
|
241
|
+
setInterval(() => {
|
|
242
|
+
if (_statusStreamClients.size === 0) return;
|
|
243
|
+
const status = getStatus();
|
|
244
|
+
const hash = require('crypto').createHash('md5').update(JSON.stringify(status)).digest('hex');
|
|
245
|
+
if (hash === _lastStatusHash) return;
|
|
246
|
+
_lastStatusHash = hash;
|
|
247
|
+
const data = JSON.stringify(status);
|
|
248
|
+
for (const res of _statusStreamClients) {
|
|
249
|
+
try { res.write('data: ' + data + '\n\n'); } catch { _statusStreamClients.delete(res); }
|
|
250
|
+
}
|
|
251
|
+
}, 10000);
|
|
252
|
+
|
|
224
253
|
|
|
225
254
|
// ── Command Center: session state + helpers ─────────────────────────────────
|
|
226
255
|
|
|
@@ -2816,6 +2845,12 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
2816
2845
|
|
|
2817
2846
|
// Status & health
|
|
2818
2847
|
{ method: 'GET', path: '/api/status', desc: 'Full dashboard status snapshot (agents, PRDs, work items, dispatch, etc.)', handler: handleStatus },
|
|
2848
|
+
{ method: 'GET', path: '/api/status-stream', desc: 'SSE stream of real-time status updates', handler: (req, res) => {
|
|
2849
|
+
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
|
|
2850
|
+
res.write('data: ' + JSON.stringify(getStatus()) + '\n\n');
|
|
2851
|
+
_statusStreamClients.add(res);
|
|
2852
|
+
req.on('close', () => _statusStreamClients.delete(res));
|
|
2853
|
+
}},
|
|
2819
2854
|
{ method: 'GET', path: '/api/health', desc: 'Lightweight health check for monitoring', handler: handleHealth },
|
|
2820
2855
|
{ method: 'GET', path: '/api/hot-reload', desc: 'SSE stream for dashboard hot-reload notifications', handler: (req, res) => {
|
|
2821
2856
|
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
|
package/engine/cli.js
CHANGED
|
@@ -320,6 +320,45 @@ const commands = {
|
|
|
320
320
|
console.log(`Tick interval: ${interval / 1000}s | Max concurrent: ${config.engine?.maxConcurrent || 5}`);
|
|
321
321
|
console.log('Press Ctrl+C to stop');
|
|
322
322
|
|
|
323
|
+
// File-change-driven work discovery — trigger tick when work-items or PRDs change
|
|
324
|
+
const _watchedFiles = new Set();
|
|
325
|
+
function watchForWorkChanges() {
|
|
326
|
+
const filesToWatch = [
|
|
327
|
+
path.join(MINIONS_DIR, 'work-items.json'),
|
|
328
|
+
path.join(ENGINE_DIR, 'dispatch.json'),
|
|
329
|
+
];
|
|
330
|
+
// Watch project-specific work-items.json
|
|
331
|
+
const { getProjects } = require('./shared');
|
|
332
|
+
for (const p of getProjects(config)) {
|
|
333
|
+
filesToWatch.push(shared.projectWorkItemsPath(p));
|
|
334
|
+
}
|
|
335
|
+
// Watch PRD files
|
|
336
|
+
const prdDir = path.join(MINIONS_DIR, 'prd');
|
|
337
|
+
try {
|
|
338
|
+
for (const f of fs.readdirSync(prdDir).filter(f => f.endsWith('.json'))) {
|
|
339
|
+
filesToWatch.push(path.join(prdDir, f));
|
|
340
|
+
}
|
|
341
|
+
} catch {}
|
|
342
|
+
|
|
343
|
+
for (const filePath of filesToWatch) {
|
|
344
|
+
if (_watchedFiles.has(filePath)) continue;
|
|
345
|
+
_watchedFiles.add(filePath);
|
|
346
|
+
try {
|
|
347
|
+
let _debounce = null;
|
|
348
|
+
fs.watchFile(filePath, { interval: 2000 }, () => {
|
|
349
|
+
// Debounce — multiple rapid writes should only trigger one tick
|
|
350
|
+
if (_debounce) return;
|
|
351
|
+
_debounce = setTimeout(() => {
|
|
352
|
+
_debounce = null;
|
|
353
|
+
e.log('info', `File change detected: ${path.basename(filePath)} — triggering tick`);
|
|
354
|
+
e.tick();
|
|
355
|
+
}, 1000);
|
|
356
|
+
});
|
|
357
|
+
} catch {}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
watchForWorkChanges();
|
|
361
|
+
|
|
323
362
|
// Graceful shutdown — wait for active agents before exiting
|
|
324
363
|
let shuttingDown = false;
|
|
325
364
|
function gracefulShutdown(signal) {
|
|
@@ -327,6 +366,7 @@ const commands = {
|
|
|
327
366
|
shuttingDown = true;
|
|
328
367
|
console.log(`\n${signal} received — initiating graceful shutdown...`);
|
|
329
368
|
clearInterval(tickTimer);
|
|
369
|
+
for (const f of _watchedFiles) { try { fs.unwatchFile(f); } catch {} }
|
|
330
370
|
safeWrite(CONTROL_PATH, { state: 'stopping', pid: process.pid, stopping_at: e.ts() });
|
|
331
371
|
e.log('info', `Graceful shutdown initiated (${signal})`);
|
|
332
372
|
|
package/package.json
CHANGED