@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 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
- dashboardUrl: `http://127.0.0.1:${DASH_PORT}/api/health`,
769
+ dashboardPid: dashProc.pid,
770
+ dashboardPort: DASH_PORT,
770
771
  });
771
772
  if (!result.ok) {
772
773
  console.error(formatRestartHealthError(result));
@@ -0,0 +1,5 @@
1
+ {
2
+ "runtime": "copilot",
3
+ "models": null,
4
+ "cachedAt": "2026-05-13T23:13:36.239Z"
5
+ }
@@ -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 = 'http://127.0.0.1:7331/api/health',
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
- const dashboard = await getJson(dashboardUrl, 3000);
96
- const dashboardStatus = dashboard && dashboard.json && dashboard.json.status;
97
- const dashboardOk = !!(dashboard && dashboard.ok && dashboardStatus === 'healthy');
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 detail = dashboard && dashboard.error
107
- ? dashboard.error.message
108
- : dashboard && dashboard.statusCode
109
- ? `HTTP ${dashboard.statusCode}${dashboardStatus ? `, status=${dashboardStatus}` : ''}`
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: { url: dashboardUrl, ok: !!(dashboard && dashboard.ok), statusCode: dashboard && dashboard.statusCode, status: dashboardStatus },
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.1922",
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"