@yemi33/minions 0.1.1797 → 0.1.1799
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 +10 -0
- package/engine/cli.js +42 -20
- package/engine/copilot-models.json +1 -1
- package/engine/timeout.js +5 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/engine/cli.js
CHANGED
|
@@ -52,6 +52,44 @@ function isEngineProcessAlive(control) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
function dispatchSafeId(itemId) {
|
|
56
|
+
return String(itemId || '').replace(/[:\\/*?"<>|]/g, '-');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readDispatchPid(itemId) {
|
|
60
|
+
const pidFile = path.join(ENGINE_DIR, 'tmp', `pid-${dispatchSafeId(itemId)}.pid`);
|
|
61
|
+
let raw;
|
|
62
|
+
try { raw = fs.readFileSync(pidFile, 'utf8').trim(); }
|
|
63
|
+
catch { return null; }
|
|
64
|
+
if (!raw) return null;
|
|
65
|
+
try { return shared.validatePid(raw); }
|
|
66
|
+
catch { return null; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isPidAlive(pid) {
|
|
70
|
+
try {
|
|
71
|
+
process.kill(shared.validatePid(pid), 0);
|
|
72
|
+
return true;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function summarizeActiveDispatchPids(activeItems = []) {
|
|
79
|
+
const summary = { live: 0, dead: 0, missing: 0 };
|
|
80
|
+
for (const item of activeItems || []) {
|
|
81
|
+
const pid = readDispatchPid(item.id);
|
|
82
|
+
if (!pid) {
|
|
83
|
+
summary.missing++;
|
|
84
|
+
} else if (isPidAlive(pid)) {
|
|
85
|
+
summary.live++;
|
|
86
|
+
} else {
|
|
87
|
+
summary.dead++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return summary;
|
|
91
|
+
}
|
|
92
|
+
|
|
55
93
|
function createControlOwner(pid = process.pid) {
|
|
56
94
|
return { pid, ownerToken: `${pid}-${shared.uid()}` };
|
|
57
95
|
}
|
|
@@ -453,21 +491,13 @@ const commands = {
|
|
|
453
491
|
e.loadCooldowns();
|
|
454
492
|
|
|
455
493
|
// Re-attach to surviving agent processes from previous session
|
|
456
|
-
const { exec } = require('./shared');
|
|
457
494
|
const dispatch = getDispatch();
|
|
458
495
|
const activeOnStart = (dispatch.active || []);
|
|
459
496
|
if (activeOnStart.length > 0) {
|
|
460
497
|
let reattached = 0;
|
|
461
498
|
for (const item of activeOnStart) {
|
|
462
499
|
const agentId = item.agent;
|
|
463
|
-
let agentPid =
|
|
464
|
-
|
|
465
|
-
const safeId = item.id.replace(/[:\\/*?"<>|]/g, '-');
|
|
466
|
-
const pidFile = path.join(ENGINE_DIR, 'tmp', `pid-${safeId}.pid`);
|
|
467
|
-
try {
|
|
468
|
-
const pidStr = fs.readFileSync(pidFile, 'utf8').trim();
|
|
469
|
-
if (pidStr) agentPid = parseInt(pidStr);
|
|
470
|
-
} catch { /* optional */ }
|
|
500
|
+
let agentPid = readDispatchPid(item.id);
|
|
471
501
|
|
|
472
502
|
if (!agentPid) {
|
|
473
503
|
const status = getAgentStatus(agentId);
|
|
@@ -484,16 +514,7 @@ const commands = {
|
|
|
484
514
|
}
|
|
485
515
|
|
|
486
516
|
const hadPid = agentPid && agentPid > 0; // track before liveness check
|
|
487
|
-
if (agentPid && agentPid > 0)
|
|
488
|
-
try {
|
|
489
|
-
if (process.platform === 'win32') {
|
|
490
|
-
const out = exec(`tasklist /FI "PID eq ${agentPid}" /NH`, { encoding: 'utf8', timeout: 3000 }).trim();
|
|
491
|
-
if (!new RegExp(`\\b${agentPid}\\b`).test(out)) agentPid = null;
|
|
492
|
-
} else {
|
|
493
|
-
process.kill(agentPid, 0);
|
|
494
|
-
}
|
|
495
|
-
} catch { agentPid = null; }
|
|
496
|
-
}
|
|
517
|
+
if (agentPid && agentPid > 0 && !isPidAlive(agentPid)) agentPid = null;
|
|
497
518
|
|
|
498
519
|
// PID was found but confirmed dead — exempt from restart grace period (#869)
|
|
499
520
|
if (hadPid && !agentPid) {
|
|
@@ -1003,7 +1024,8 @@ const commands = {
|
|
|
1003
1024
|
const metrics = shared.safeJson(path.join(__dirname, 'metrics.json')) || {};
|
|
1004
1025
|
const lifetimeCompleted = Object.entries(metrics).filter(([k]) => !k.startsWith('_')).reduce((sum, [, m]) => sum + (m.tasksCompleted || 0) + (m.tasksErrored || 0), 0);
|
|
1005
1026
|
console.log(`Dispatch: ${dispatch.pending.length} pending | ${(dispatch.active || []).length} active | ${lifetimeCompleted} completed`);
|
|
1006
|
-
|
|
1027
|
+
const pidSummary = summarizeActiveDispatchPids(dispatch.active || []);
|
|
1028
|
+
console.log(`Active dispatch PID files: ${pidSummary.live} live | ${pidSummary.dead} dead | ${pidSummary.missing} missing`);
|
|
1007
1029
|
|
|
1008
1030
|
const metricsPath = path.join(ENGINE_DIR, 'metrics.json');
|
|
1009
1031
|
const metricsData = safeJson(metricsPath);
|
package/engine/timeout.js
CHANGED
|
@@ -452,7 +452,9 @@ function checkTimeouts(config) {
|
|
|
452
452
|
} catch { /* ENOENT — keep default */ }
|
|
453
453
|
|
|
454
454
|
const confirmedDeadAtRestart = engineRestartGraceExempt?.has(item.id);
|
|
455
|
-
|
|
455
|
+
const reattachedProcessEnded = !!procInfo?.reattached && !processAlive;
|
|
456
|
+
const canReapDeadProcess = confirmedDeadAtRestart || reattachedProcessEnded;
|
|
457
|
+
if (!processAlive && (canReapDeadProcess || silentMs > staleOrphanTimeout) && (Date.now() > engineRestartGraceUntil || canReapDeadProcess)) {
|
|
456
458
|
// Last-resort PID check: lost tracked handle but OS process may still be alive.
|
|
457
459
|
if (isOsPidAliveForDispatch(item.id)) {
|
|
458
460
|
log('info', `Orphan check: ${item.agent} (${item.id}) silent ${silentSec}s but OS PID is alive — keeping [${_logState}]`);
|
|
@@ -477,7 +479,8 @@ function checkTimeouts(config) {
|
|
|
477
479
|
}
|
|
478
480
|
} catch (e) { log('warn', 'orphan final output completion scan: ' + e.message); }
|
|
479
481
|
|
|
480
|
-
// No tracked process AND no recent output past stale-orphan timeout AND (grace period expired OR confirmed-dead at restart)
|
|
482
|
+
// No tracked process AND no recent output past stale-orphan timeout AND (grace period expired OR confirmed-dead at restart)
|
|
483
|
+
// OR a re-attached process whose OS PID has since exited (no close event will fire for fake restart handles) → orphaned
|
|
481
484
|
log('warn', `Orphan detected: ${item.agent} (${item.id}) — no live process tracked, silent for ${silentSec}s [logExists/logSize=${_logState}]`);
|
|
482
485
|
dispatch().updateAgentStatus(item.id, AGENT_STATUS.TIMED_OUT, `Orphaned — no process, silent for ${silentSec}s`);
|
|
483
486
|
// Clear session so retry starts fresh
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1799",
|
|
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"
|