@yemi33/minions 0.1.2387 → 0.1.2389
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/dashboard/js/render-plans.js +1 -1
- package/engine/check-status.js +2 -4
- package/engine/consolidation.js +1 -1
- package/engine/queries.js +2 -2
- package/engine/supervisor.js +48 -11
- package/package.json +1 -1
|
@@ -627,7 +627,7 @@ function _renderPlanModal(normalizedFile, raw, lastMod) {
|
|
|
627
627
|
text = 'Project: ' + (plan.project || '?') +
|
|
628
628
|
'\nStrategy: ' + (plan.branch_strategy || 'parallel') +
|
|
629
629
|
'\nBranch: ' + (plan.feature_branch || 'per-item') +
|
|
630
|
-
'\nStatus: ' + (plan.status || 'active') +
|
|
630
|
+
'\nStatus: ' + (plan.status || (plan.requires_approval && !plan.approvedAt ? 'awaiting-approval' : 'active')) +
|
|
631
631
|
'\nGenerated by: ' + (plan.generated_by || '?') + ' on ' + (plan.generated_at || '?') +
|
|
632
632
|
'\n\n--- Items (' + (plan.missing_features || []).length + ') ---\n\n' + items +
|
|
633
633
|
(plan.open_questions?.length ? '\n\n--- Open Questions ---\n\n' + plan.open_questions.map(q => '\u2022 ' + q).join('\n') : '');
|
package/engine/check-status.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const dir = path.resolve(__dirname, '..');
|
|
4
3
|
const { safeJson, MINIONS_DIR, DONE_STATUSES } = require('./shared');
|
|
5
4
|
|
|
6
5
|
console.log('=== Work Items (non-done) ===');
|
|
@@ -12,8 +11,7 @@ items.filter(i => !DONE_STATUSES.has(i.status)).forEach(i => {
|
|
|
12
11
|
|
|
13
12
|
console.log('\n=== Agent Status (derived from dispatch) ===');
|
|
14
13
|
const { getAgentStatus } = require('./queries');
|
|
15
|
-
|
|
16
|
-
try { config = safeJson(path.join(MINIONS_DIR, 'config.json')) || {}; } catch {}
|
|
14
|
+
const config = safeJson(path.join(MINIONS_DIR, 'config.json')) || {};
|
|
17
15
|
const agents = Object.keys(config.agents || {});
|
|
18
16
|
if (agents.length === 0) {
|
|
19
17
|
console.log('(no agents configured)');
|
|
@@ -26,6 +24,6 @@ if (agents.length === 0) {
|
|
|
26
24
|
|
|
27
25
|
console.log('\n=== Inbox ===');
|
|
28
26
|
try {
|
|
29
|
-
const inbox = fs.readdirSync(path.join(
|
|
27
|
+
const inbox = fs.readdirSync(path.join(MINIONS_DIR, 'notes', 'inbox')).filter(f => f.endsWith('.md'));
|
|
30
28
|
console.log(inbox.length + ' files:', inbox.join(', '));
|
|
31
29
|
} catch { console.log('empty'); }
|
package/engine/consolidation.js
CHANGED
|
@@ -42,7 +42,7 @@ function _capNotesPreservingOverflow(newContent) {
|
|
|
42
42
|
// No section boundary before the cap — fall back to keeping header + the
|
|
43
43
|
// most recent sections, archiving the middle.
|
|
44
44
|
const sections = newContent.split('\n---\n\n### ');
|
|
45
|
-
if (sections.length >
|
|
45
|
+
if (sections.length > 9) {
|
|
46
46
|
kept = sections[0] + '\n---\n\n### ' + sections.slice(-8).join('\n---\n\n### ');
|
|
47
47
|
const middle = sections.slice(1, -8);
|
|
48
48
|
if (middle.length) dropped = '\n---\n\n### ' + middle.join('\n---\n\n### ');
|
package/engine/queries.js
CHANGED
|
@@ -2176,7 +2176,7 @@ function getPrdInfo(config) {
|
|
|
2176
2176
|
}
|
|
2177
2177
|
existingPrds.push({
|
|
2178
2178
|
file: pf,
|
|
2179
|
-
status: plan.status || 'active',
|
|
2179
|
+
status: plan.status || (plan.requires_approval && !plan.approvedAt ? 'awaiting-approval' : 'active'),
|
|
2180
2180
|
planStale: planStale || plan.planStale || false,
|
|
2181
2181
|
completedAt: plan.completedAt || '',
|
|
2182
2182
|
_archived: isArch,
|
|
@@ -2195,7 +2195,7 @@ function getPrdInfo(config) {
|
|
|
2195
2195
|
? (prdItemIdByKey.get(`${archived ? 1 : 0} ${pf} ${f.id}`) ?? null)
|
|
2196
2196
|
: null;
|
|
2197
2197
|
allPrdItems.push({
|
|
2198
|
-
...f, _source: pf, _planStatus: plan.status || 'active',
|
|
2198
|
+
...f, _source: pf, _planStatus: plan.status || (plan.requires_approval && !plan.approvedAt ? 'awaiting-approval' : 'active'),
|
|
2199
2199
|
_planSummary: plan.plan_summary || pf, _planProject: plan.project || '',
|
|
2200
2200
|
_archived: isArch, _sourcePlan: plan.source_plan || '',
|
|
2201
2201
|
_branchStrategy: plan.branch_strategy || 'parallel',
|
package/engine/supervisor.js
CHANGED
|
@@ -507,7 +507,39 @@ function _isRespawnEligibleEngineState(state) {
|
|
|
507
507
|
return state === 'running' || state === 'degraded';
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
-
|
|
510
|
+
const ENGINE_WATCHDOG_OPERATION_NAMES = [
|
|
511
|
+
'isPidAlive',
|
|
512
|
+
'reapStrayProcesses',
|
|
513
|
+
'spawnEngine',
|
|
514
|
+
'recordCrashLoopRespawn',
|
|
515
|
+
];
|
|
516
|
+
|
|
517
|
+
// Isolated state does not isolate this module's source checkout, so tests must
|
|
518
|
+
// provide every OS/process side effect before an engine respawn is possible.
|
|
519
|
+
function _resolveEngineWatchdogOperations(injected) {
|
|
520
|
+
const provided = injected && typeof injected === 'object' ? injected : {};
|
|
521
|
+
if (process.env.MINIONS_TEST_DIR) {
|
|
522
|
+
const missing = ENGINE_WATCHDOG_OPERATION_NAMES
|
|
523
|
+
.filter(name => typeof provided[name] !== 'function');
|
|
524
|
+
if (missing.length > 0) {
|
|
525
|
+
throw new Error(
|
|
526
|
+
`MINIONS_TEST_DIR is set; inject engine watchdog operations instead of using real process control: ${missing.join(', ')}`,
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return {
|
|
531
|
+
isPidAlive: typeof provided.isPidAlive === 'function' ? provided.isPidAlive : isPidAlive,
|
|
532
|
+
reapStrayProcesses: typeof provided.reapStrayProcesses === 'function'
|
|
533
|
+
? provided.reapStrayProcesses
|
|
534
|
+
: reapStrayProcesses,
|
|
535
|
+
spawnEngine: typeof provided.spawnEngine === 'function' ? provided.spawnEngine : spawnEngine,
|
|
536
|
+
recordCrashLoopRespawn: typeof provided.recordCrashLoopRespawn === 'function'
|
|
537
|
+
? provided.recordCrashLoopRespawn
|
|
538
|
+
: _recordCrashLoopRespawn,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function checkEngine(now, operations) {
|
|
511
543
|
if (now - _lastEngineRespawnAt < POST_SPAWN_GRACE_MS) return;
|
|
512
544
|
const control = safeReadJson(CONTROL_PATH_FN());
|
|
513
545
|
// Respawn when control.json says "running" (PID died unexpectedly) or
|
|
@@ -515,7 +547,6 @@ function checkEngine(now) {
|
|
|
515
547
|
// above). "paused"/"stopped"/"stopping" are legitimate user-intent states the
|
|
516
548
|
// supervisor must not override.
|
|
517
549
|
if (!control || !_isRespawnEligibleEngineState(control.state)) return;
|
|
518
|
-
if (control.pid && isPidAlive(control.pid)) return;
|
|
519
550
|
|
|
520
551
|
// Cross-watchdog race guard: dashboard.js's in-process engine watchdog
|
|
521
552
|
// writes `{pid: null, restarted_at}` BEFORE spawning a new engine. During
|
|
@@ -530,13 +561,16 @@ function checkEngine(now) {
|
|
|
530
561
|
}
|
|
531
562
|
}
|
|
532
563
|
|
|
564
|
+
const ops = _resolveEngineWatchdogOperations(operations);
|
|
565
|
+
if (control.pid && ops.isPidAlive(control.pid)) return;
|
|
566
|
+
|
|
533
567
|
console.log(`[supervisor] Engine PID ${control.pid || '(none)'} is dead — respawning...`);
|
|
534
568
|
// Reap any orphan engines first so a stale control.json PID can't snowball
|
|
535
569
|
// into an unbounded pile of contending engine processes.
|
|
536
|
-
reapStrayProcesses(path.join(MINIONS_DIR, 'engine.js'), 'engine');
|
|
537
|
-
const newPid = spawnEngine();
|
|
570
|
+
ops.reapStrayProcesses(path.join(MINIONS_DIR, 'engine.js'), 'engine');
|
|
571
|
+
const newPid = ops.spawnEngine();
|
|
538
572
|
_lastEngineRespawnAt = now;
|
|
539
|
-
|
|
573
|
+
ops.recordCrashLoopRespawn('supervisor:dead-pid');
|
|
540
574
|
console.log(`[supervisor] Engine respawned (new PID: ${newPid})`);
|
|
541
575
|
}
|
|
542
576
|
|
|
@@ -546,7 +580,7 @@ function checkEngine(now) {
|
|
|
546
580
|
// is alive but control.heartbeat has not advanced for SUPERVISOR_STALE_ENGINE_HEARTBEAT_MS.
|
|
547
581
|
// The heartbeat is written every ~15s by a dedicated timer in engine/cli.js; if
|
|
548
582
|
// it goes stale while the PID persists, the event loop is stuck.
|
|
549
|
-
function checkEngineHung(now) {
|
|
583
|
+
function checkEngineHung(now, operations) {
|
|
550
584
|
if (now - _lastEngineRespawnAt < POST_SPAWN_GRACE_MS) return;
|
|
551
585
|
const control = safeReadJson(CONTROL_PATH_FN());
|
|
552
586
|
// Include 'degraded' (see _isRespawnEligibleEngineState) so a permanently
|
|
@@ -554,8 +588,6 @@ function checkEngineHung(now) {
|
|
|
554
588
|
// re-enters) still gets force-restarted once its own heartbeat goes stale,
|
|
555
589
|
// instead of being ignored forever the moment the state flips to 'degraded'.
|
|
556
590
|
if (!control || !_isRespawnEligibleEngineState(control.state)) return;
|
|
557
|
-
// Only acts when the PID is alive — a dead PID is handled by checkEngine().
|
|
558
|
-
if (!control.pid || !isPidAlive(control.pid)) return;
|
|
559
591
|
|
|
560
592
|
const heartbeat = Number(control.heartbeat);
|
|
561
593
|
if (!heartbeat || !Number.isFinite(heartbeat)) return; // no heartbeat yet (fresh start)
|
|
@@ -563,15 +595,20 @@ function checkEngineHung(now) {
|
|
|
563
595
|
const heartbeatAge = now - heartbeat;
|
|
564
596
|
if (heartbeatAge < SUPERVISOR_STALE_ENGINE_HEARTBEAT_MS) return;
|
|
565
597
|
|
|
598
|
+
// Only acts when the PID is alive — a dead PID is handled by checkEngine().
|
|
599
|
+
if (!control.pid) return;
|
|
600
|
+
const ops = _resolveEngineWatchdogOperations(operations);
|
|
601
|
+
if (!ops.isPidAlive(control.pid)) return;
|
|
602
|
+
|
|
566
603
|
console.log(
|
|
567
604
|
`[supervisor] Engine PID ${control.pid} is alive but heartbeat is stale ` +
|
|
568
605
|
`(${Math.round(heartbeatAge / 1000)}s old, threshold ${SUPERVISOR_STALE_ENGINE_HEARTBEAT_MS / 1000}s) — ` +
|
|
569
606
|
`event loop likely frozen; restarting engine`,
|
|
570
607
|
);
|
|
571
|
-
reapStrayProcesses(path.join(MINIONS_DIR, 'engine.js'), 'engine');
|
|
572
|
-
const newPid = spawnEngine();
|
|
608
|
+
ops.reapStrayProcesses(path.join(MINIONS_DIR, 'engine.js'), 'engine');
|
|
609
|
+
const newPid = ops.spawnEngine();
|
|
573
610
|
_lastEngineRespawnAt = now;
|
|
574
|
-
|
|
611
|
+
ops.recordCrashLoopRespawn('supervisor:stale-heartbeat');
|
|
575
612
|
console.log(`[supervisor] Engine restarted due to stale heartbeat (new PID: ${newPid})`);
|
|
576
613
|
}
|
|
577
614
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2389",
|
|
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"
|