@yemi33/minions 0.1.1922 → 0.1.1923
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/bin/minions.js +2 -1
- package/engine/copilot-models.json +5 -0
- package/engine/restart-health.js +65 -11
- package/package.json +1 -1
package/bin/minions.js
CHANGED
|
@@ -766,7 +766,8 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
766
766
|
void (async () => {
|
|
767
767
|
const result = await waitForRestartHealth({
|
|
768
768
|
minionsHome: MINIONS_HOME,
|
|
769
|
-
|
|
769
|
+
dashboardPid: dashProc.pid,
|
|
770
|
+
dashboardPort: DASH_PORT,
|
|
770
771
|
});
|
|
771
772
|
if (!result.ok) {
|
|
772
773
|
console.error(formatRestartHealthError(result));
|
package/engine/restart-health.js
CHANGED
|
@@ -43,6 +43,25 @@ function isProcessAlive(pid) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function isPortListening(port) {
|
|
47
|
+
const n = Number(port);
|
|
48
|
+
if (!Number.isInteger(n) || n <= 0) return false;
|
|
49
|
+
try {
|
|
50
|
+
if (process.platform === 'win32') {
|
|
51
|
+
const out = execSync(`netstat -ano -p TCP`, {
|
|
52
|
+
encoding: 'utf8', windowsHide: true, timeout: 3000, maxBuffer: 4 * 1024 * 1024,
|
|
53
|
+
});
|
|
54
|
+
const re = new RegExp(`\\s127\\.0\\.0\\.1:${n}\\s+\\S+\\s+LISTENING`, 'i');
|
|
55
|
+
const re6 = new RegExp(`\\s\\[::1?\\]:${n}\\s+\\S+\\s+LISTENING`, 'i');
|
|
56
|
+
return re.test(out) || re6.test(out);
|
|
57
|
+
}
|
|
58
|
+
const out = execSync(`ss -ltn 'sport = :${n}' 2>/dev/null || netstat -ltn 2>/dev/null`, {
|
|
59
|
+
encoding: 'utf8', timeout: 3000, shell: true,
|
|
60
|
+
});
|
|
61
|
+
return new RegExp(`[:.]${n}\\b`).test(out);
|
|
62
|
+
} catch { return false; }
|
|
63
|
+
}
|
|
64
|
+
|
|
46
65
|
function httpGetJson(url, timeoutMs = 1000) {
|
|
47
66
|
return new Promise(resolve => {
|
|
48
67
|
let settled = false;
|
|
@@ -81,9 +100,12 @@ function httpGetJson(url, timeoutMs = 1000) {
|
|
|
81
100
|
async function checkRestartHealth(options = {}) {
|
|
82
101
|
const {
|
|
83
102
|
minionsHome,
|
|
84
|
-
dashboardUrl
|
|
103
|
+
dashboardUrl,
|
|
104
|
+
dashboardPid,
|
|
105
|
+
dashboardPort = 7331,
|
|
85
106
|
readControl = readEngineControl,
|
|
86
107
|
isProcessAlive: isAlive = isProcessAlive,
|
|
108
|
+
isPortListening: portCheck = isPortListening,
|
|
87
109
|
httpGetJson: getJson = httpGetJson,
|
|
88
110
|
} = options;
|
|
89
111
|
|
|
@@ -92,9 +114,42 @@ async function checkRestartHealth(options = {}) {
|
|
|
92
114
|
const engineAlive = pid ? isAlive(pid) : false;
|
|
93
115
|
const engineOk = control && control.state === 'running' && engineAlive;
|
|
94
116
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
117
|
+
// Two strategies for the dashboard check:
|
|
118
|
+
// 1) Process + port-listening (preferred from `minions restart` since
|
|
119
|
+
// `bin/minions.js` knows the dashboard PID it just spawned). This avoids
|
|
120
|
+
// a roundtrip through the dashboard's Node event loop, which can be
|
|
121
|
+
// blocked for 15-25s during a cold `getStatus()` rebuild while still
|
|
122
|
+
// being healthy.
|
|
123
|
+
// 2) HTTP `/api/health` — legacy path retained for tests that mock
|
|
124
|
+
// httpGetJson + dashboardUrl, and for external probes that genuinely
|
|
125
|
+
// want a response-level check.
|
|
126
|
+
// Strategy 1 wins when `dashboardPid` is supplied; otherwise we fall back to
|
|
127
|
+
// HTTP using the default URL or whatever the caller injected.
|
|
128
|
+
let dashboardOk;
|
|
129
|
+
let dashboardDetail;
|
|
130
|
+
let dashboardKind;
|
|
131
|
+
let dashboardSnapshot;
|
|
132
|
+
if (dashboardPid != null && !dashboardUrl) {
|
|
133
|
+
dashboardKind = 'process';
|
|
134
|
+
const dpid = normalizePid(dashboardPid);
|
|
135
|
+
const dashAlive = dpid ? isAlive(dpid) : false;
|
|
136
|
+
const portOpen = portCheck(dashboardPort);
|
|
137
|
+
dashboardOk = !!(dashAlive && portOpen);
|
|
138
|
+
dashboardDetail = `pid=${dpid || 'none'} alive=${dashAlive ? 'yes' : 'no'} port=${dashboardPort} listening=${portOpen ? 'yes' : 'no'}`;
|
|
139
|
+
dashboardSnapshot = { kind: 'process', pid: dpid, alive: dashAlive, port: dashboardPort, listening: portOpen };
|
|
140
|
+
} else {
|
|
141
|
+
dashboardKind = 'http';
|
|
142
|
+
const url = dashboardUrl || `http://127.0.0.1:${dashboardPort}/api/health`;
|
|
143
|
+
const dashboard = await getJson(url, 3000);
|
|
144
|
+
const dashboardStatus = dashboard && dashboard.json && dashboard.json.status;
|
|
145
|
+
dashboardOk = !!(dashboard && dashboard.ok && dashboardStatus === 'healthy');
|
|
146
|
+
dashboardDetail = dashboard && dashboard.error
|
|
147
|
+
? dashboard.error.message
|
|
148
|
+
: dashboard && dashboard.statusCode
|
|
149
|
+
? `HTTP ${dashboard.statusCode}${dashboardStatus ? `, status=${dashboardStatus}` : ''}`
|
|
150
|
+
: 'no response';
|
|
151
|
+
dashboardSnapshot = { kind: 'http', url, ok: !!(dashboard && dashboard.ok), statusCode: dashboard && dashboard.statusCode, status: dashboardStatus };
|
|
152
|
+
}
|
|
98
153
|
|
|
99
154
|
const errors = [];
|
|
100
155
|
if (!engineOk) {
|
|
@@ -103,18 +158,16 @@ async function checkRestartHealth(options = {}) {
|
|
|
103
158
|
errors.push(`Engine is not healthy (state=${state}, pid=${pidLabel}, alive=${engineAlive ? 'yes' : 'no'})`);
|
|
104
159
|
}
|
|
105
160
|
if (!dashboardOk) {
|
|
106
|
-
const
|
|
107
|
-
?
|
|
108
|
-
: dashboard
|
|
109
|
-
|
|
110
|
-
: 'no response';
|
|
111
|
-
errors.push(`Dashboard failed health check at ${dashboardUrl} (${detail})`);
|
|
161
|
+
const where = dashboardKind === 'http'
|
|
162
|
+
? (dashboardUrl || `http://127.0.0.1:${dashboardPort}/api/health`)
|
|
163
|
+
: `dashboard pid=${normalizePid(dashboardPid) || 'none'} port=${dashboardPort}`;
|
|
164
|
+
errors.push(`Dashboard failed health check at ${where} (${dashboardDetail})`);
|
|
112
165
|
}
|
|
113
166
|
|
|
114
167
|
return {
|
|
115
168
|
ok: engineOk && dashboardOk,
|
|
116
169
|
engine: { state: control && control.state, pid, alive: engineAlive },
|
|
117
|
-
dashboard:
|
|
170
|
+
dashboard: dashboardSnapshot,
|
|
118
171
|
errors,
|
|
119
172
|
};
|
|
120
173
|
}
|
|
@@ -163,6 +216,7 @@ module.exports = {
|
|
|
163
216
|
_private: {
|
|
164
217
|
httpGetJson,
|
|
165
218
|
isProcessAlive,
|
|
219
|
+
isPortListening,
|
|
166
220
|
readEngineControl,
|
|
167
221
|
normalizePid,
|
|
168
222
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1923",
|
|
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"
|