@yemi33/minions 0.1.1826 → 0.1.1828
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/bin/minions.js +39 -77
- package/dashboard.js +5 -13
- package/engine/copilot-models.json +1 -1
- package/engine/shared.js +16 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1828 (2026-05-09)
|
|
4
|
+
|
|
5
|
+
### Other
|
|
6
|
+
- refactor(cli): atomic clear, soft-fail browser-open, fix stale test message
|
|
7
|
+
|
|
8
|
+
## 0.1.1827 (2026-05-09)
|
|
9
|
+
|
|
10
|
+
### Other
|
|
11
|
+
- refactor(cli): consolidate browser-open helpers and tighten restart flow
|
|
12
|
+
|
|
3
13
|
## 0.1.1826 (2026-05-09)
|
|
4
14
|
|
|
5
15
|
### Fixes
|
package/bin/minions.js
CHANGED
|
@@ -40,9 +40,9 @@ const { spawn, spawnSync, execSync } = require('child_process');
|
|
|
40
40
|
|
|
41
41
|
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
42
42
|
const shared = require(path.join(PKG_ROOT, 'engine', 'shared'));
|
|
43
|
+
const { openUrlInBrowser } = shared;
|
|
43
44
|
const { waitForRestartHealth, formatRestartHealthError } = require(path.join(PKG_ROOT, 'engine', 'restart-health'));
|
|
44
45
|
const DASH_PORT = 7331;
|
|
45
|
-
const DASHBOARD_BROWSER_PRESENCE_MAX_AGE_MS = 45000;
|
|
46
46
|
|
|
47
47
|
/** Returns PIDs (as strings) of processes LISTENING on `port`. Empty on no match
|
|
48
48
|
* or when the platform tool (netstat/findstr/lsof) is unavailable. */
|
|
@@ -74,28 +74,16 @@ function killByPort(port) {
|
|
|
74
74
|
|
|
75
75
|
const isPortListening = (port) => getListeningPids(port).length > 0;
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const tabs = state && state.tabs && typeof state.tabs === 'object' ? state.tabs : {};
|
|
82
|
-
return Object.values(tabs).some(tab => {
|
|
83
|
-
const lastSeen = Number(tab && tab.lastSeen);
|
|
84
|
-
return Number.isFinite(lastSeen) && now - lastSeen <= DASHBOARD_BROWSER_PRESENCE_MAX_AGE_MS;
|
|
85
|
-
});
|
|
86
|
-
} catch { return false; }
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Clear the browser-presence cache. Pre-restart beacons can outlive the
|
|
90
|
-
// browser window that produced them (closed Edge, locked-screen RDP session,
|
|
91
|
-
// stale heuristic timer) and falsely tell `restart` to skip the auto-open. We
|
|
92
|
-
// wipe it during restart so the post-restart probe only counts beacons that
|
|
93
|
-
// arrive AFTER the new dashboard is up — i.e. from a still-living tab.
|
|
77
|
+
// Pre-restart beacons can outlive the browser window that produced them
|
|
78
|
+
// (closed Edge, locked-screen RDP session) and falsely tell restart to skip
|
|
79
|
+
// the auto-open. We wipe the file during restart so the post-restart probe
|
|
80
|
+
// only counts beacons that arrive AFTER the new dashboard is up.
|
|
94
81
|
function _clearDashboardBrowserState(minionsHome) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
82
|
+
// Atomic write so the new dashboard can't read a torn file mid-write.
|
|
83
|
+
shared.safeWrite(
|
|
84
|
+
path.join(minionsHome, 'engine', 'dashboard-browser.json'),
|
|
85
|
+
{ tabs: {}, updatedAt: new Date().toISOString() },
|
|
86
|
+
);
|
|
99
87
|
}
|
|
100
88
|
|
|
101
89
|
async function _waitForBrowserReconnect(minionsHome, { afterMs, timeoutMs = 5000, pollMs = 500 } = {}) {
|
|
@@ -113,12 +101,9 @@ async function _waitForBrowserReconnect(minionsHome, { afterMs, timeoutMs = 5000
|
|
|
113
101
|
}
|
|
114
102
|
|
|
115
103
|
function _openInBrowser(url) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
else execSync(`xdg-open "${url}"`, { stdio: 'ignore' });
|
|
120
|
-
} catch (e) {
|
|
121
|
-
console.log(` Could not auto-open browser: ${e.message}`);
|
|
104
|
+
const result = openUrlInBrowser(url);
|
|
105
|
+
if (!result.ok) {
|
|
106
|
+
console.log(` Could not auto-open browser: ${result.error}`);
|
|
122
107
|
console.log(` Please open ${url} manually.`);
|
|
123
108
|
}
|
|
124
109
|
}
|
|
@@ -180,12 +165,10 @@ function killMinionsProcesses(patterns) {
|
|
|
180
165
|
} catch {}
|
|
181
166
|
}
|
|
182
167
|
|
|
183
|
-
/** Spawn a detached dashboard
|
|
184
|
-
*
|
|
185
|
-
function spawnDashboard(
|
|
186
|
-
const env = { ...process.env };
|
|
187
|
-
if (suppressOpen) env.MINIONS_NO_AUTO_OPEN = '1';
|
|
188
|
-
else delete env.MINIONS_NO_AUTO_OPEN;
|
|
168
|
+
/** Spawn a detached dashboard with self-open suppressed — the CLI decides
|
|
169
|
+
* when to open a browser based on whether a real tab reconnects post-health. */
|
|
170
|
+
function spawnDashboard() {
|
|
171
|
+
const env = { ...process.env, MINIONS_NO_AUTO_OPEN: '1' };
|
|
189
172
|
const proc = spawn(process.execPath, [path.join(MINIONS_HOME, 'dashboard.js')], {
|
|
190
173
|
cwd: MINIONS_HOME, stdio: 'ignore', detached: true, windowsHide: true, env
|
|
191
174
|
});
|
|
@@ -451,13 +434,12 @@ function init() {
|
|
|
451
434
|
const dashWasUp = isPortListening(DASH_PORT);
|
|
452
435
|
const restartStartMs = Date.now();
|
|
453
436
|
if (isUpgrade) {
|
|
454
|
-
// Clear stale beacons so the post-restart probe only counts tabs that
|
|
455
|
-
// reconnect to the NEW dashboard.
|
|
456
|
-
_clearDashboardBrowserState(MINIONS_HOME);
|
|
457
437
|
try { execSync(`node "${path.join(MINIONS_HOME, 'engine.js')}" stop`, { stdio: 'ignore', cwd: MINIONS_HOME, timeout: 10000, windowsHide: true }); } catch {}
|
|
458
438
|
// Free the dashboard port too — without this the new dashboard EADDRINUSE-dies
|
|
459
439
|
// silently and the user keeps running stale code from the old dashboard process.
|
|
460
440
|
killByPort(DASH_PORT);
|
|
441
|
+
// Clear AFTER kill so the old dashboard can't repopulate during shutdown.
|
|
442
|
+
_clearDashboardBrowserState(MINIONS_HOME);
|
|
461
443
|
}
|
|
462
444
|
console.log(isUpgrade
|
|
463
445
|
? `\n Upgrade complete (${pkgVersion}). Restarting engine and dashboard...\n`
|
|
@@ -468,27 +450,20 @@ function init() {
|
|
|
468
450
|
engineProc.unref();
|
|
469
451
|
console.log(` Engine started (PID: ${engineProc.pid})`);
|
|
470
452
|
|
|
471
|
-
|
|
472
|
-
// check based on whether an existing browser tab actually reconnects.
|
|
473
|
-
const dashProc = spawnDashboard(true);
|
|
453
|
+
const dashProc = spawnDashboard();
|
|
474
454
|
console.log(` Dashboard started (PID: ${dashProc.pid})`);
|
|
475
455
|
console.log(` Dashboard: http://localhost:${DASH_PORT}`);
|
|
476
456
|
|
|
477
457
|
void (async () => {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
// Hot upgrade: give an existing browser tab up to 5s to reconnect via
|
|
481
|
-
// its 4s auto-refresh poll. If nothing beacons, the tab is gone — open.
|
|
482
|
-
const reconnected = await _waitForBrowserReconnect(MINIONS_HOME, {
|
|
483
|
-
afterMs: restartStartMs, timeoutMs: 5000,
|
|
484
|
-
});
|
|
485
|
-
shouldOpen = !reconnected;
|
|
486
|
-
}
|
|
458
|
+
const shouldOpen = forceOpen || !dashWasUp ||
|
|
459
|
+
!(await _waitForBrowserReconnect(MINIONS_HOME, { afterMs: restartStartMs, timeoutMs: 5000 }));
|
|
487
460
|
if (shouldOpen) {
|
|
488
461
|
console.log(` Opening dashboard in browser...`);
|
|
489
462
|
_openInBrowser(`http://localhost:${DASH_PORT}`);
|
|
490
463
|
}
|
|
491
|
-
})()
|
|
464
|
+
})().catch(err => {
|
|
465
|
+
console.log(` Could not open dashboard: ${err.message}`);
|
|
466
|
+
});
|
|
492
467
|
|
|
493
468
|
// Next steps guidance
|
|
494
469
|
console.log(`
|
|
@@ -740,36 +715,30 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
740
715
|
} else if (cmd === 'add' || cmd === 'remove' || cmd === 'list' || cmd === 'scan') {
|
|
741
716
|
delegate('minions.js', [cmd, ...rest]);
|
|
742
717
|
} else if (cmd === 'restart') {
|
|
743
|
-
// Start both engine and dashboard — the go-to command after a reboot.
|
|
744
718
|
// `--cli` / `--model` flags forward to `engine.js start` so the runtime
|
|
745
719
|
// fleet flips before the daemon spawns (P-6b3f9c2e AC: works on restart).
|
|
746
720
|
ensureInstalled();
|
|
747
721
|
const dashWasUp = isPortListening(DASH_PORT);
|
|
748
|
-
// Mark the restart boundary so post-restart beacon timestamps are unambiguous,
|
|
749
|
-
// and clear stale beacons (browsers that closed without notifying).
|
|
750
722
|
const restartStartMs = Date.now();
|
|
751
|
-
|
|
752
|
-
//
|
|
753
|
-
// one fails. Goal: the old engine is gone before we spawn a new one, even if
|
|
723
|
+
// Layered kill — each step is best-effort, so the next still runs if one
|
|
724
|
+
// fails. Goal: the old engine is gone before we spawn a new one, even if
|
|
754
725
|
// PowerShell is unavailable, the engine is hung, or its cmdline doesn't match.
|
|
755
726
|
const oldEnginePid = readEnginePid(MINIONS_HOME);
|
|
756
|
-
// 1. Graceful stop — short timeout so a hung engine can't block what follows.
|
|
757
727
|
try { execSync(`node "${path.join(MINIONS_HOME, 'engine.js')}" stop`, { stdio: 'ignore', cwd: MINIONS_HOME, timeout: 10000, windowsHide: true }); } catch {}
|
|
758
|
-
//
|
|
759
|
-
//
|
|
728
|
+
// Force-kill the recorded engine PID (NOT the tree — agent children must
|
|
729
|
+
// survive so the new engine can re-attach them via PID files).
|
|
760
730
|
killPidOnly(oldEnginePid);
|
|
761
|
-
// 3. Free dashboard port (catches orphan dashboards with no recorded PID).
|
|
762
731
|
killByPort(DASH_PORT);
|
|
763
|
-
// 4. Belt-and-suspenders cmdline match for anything still alive.
|
|
764
732
|
killMinionsProcesses(['engine.js', 'dashboard.js']);
|
|
733
|
+
// Clear stale beacons AFTER the kill so the old dashboard's last writes
|
|
734
|
+
// can't repopulate the file in the gap between clear and shutdown.
|
|
735
|
+
_clearDashboardBrowserState(MINIONS_HOME);
|
|
765
736
|
const engineProc = spawn(process.execPath, [path.join(MINIONS_HOME, 'engine.js'), 'start', ...rest], {
|
|
766
737
|
cwd: MINIONS_HOME, stdio: 'ignore', detached: true, windowsHide: true
|
|
767
738
|
});
|
|
768
739
|
engineProc.unref();
|
|
769
740
|
console.log(`\n Engine started (PID: ${engineProc.pid})`);
|
|
770
|
-
|
|
771
|
-
// after observing whether an existing browser tab reconnects.
|
|
772
|
-
const dashProc = spawnDashboard(true);
|
|
741
|
+
const dashProc = spawnDashboard();
|
|
773
742
|
console.log(` Dashboard started (PID: ${dashProc.pid})`);
|
|
774
743
|
console.log(` Dashboard: http://localhost:${DASH_PORT}`);
|
|
775
744
|
console.log(' Verifying restart health...');
|
|
@@ -784,21 +753,17 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
784
753
|
}
|
|
785
754
|
console.log(` Restart verified: engine PID ${result.engine.pid}; dashboard healthy.`);
|
|
786
755
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
const reconnected = await _waitForBrowserReconnect(MINIONS_HOME, {
|
|
790
|
-
afterMs: restartStartMs, timeoutMs: 5000,
|
|
791
|
-
});
|
|
792
|
-
shouldOpen = !reconnected;
|
|
793
|
-
}
|
|
756
|
+
const shouldOpen = forceOpen || !dashWasUp ||
|
|
757
|
+
!(await _waitForBrowserReconnect(MINIONS_HOME, { afterMs: restartStartMs, timeoutMs: 5000 }));
|
|
794
758
|
if (shouldOpen) {
|
|
795
759
|
console.log(` Opening dashboard in browser...`);
|
|
796
760
|
_openInBrowser(`http://localhost:${DASH_PORT}`);
|
|
797
761
|
}
|
|
798
762
|
console.log('');
|
|
799
763
|
})().catch(err => {
|
|
800
|
-
|
|
801
|
-
|
|
764
|
+
// Health check failures already process.exit(1) above; reaching here means
|
|
765
|
+
// the browser-open path threw, which is non-fatal — restart already verified.
|
|
766
|
+
console.log(` Could not open dashboard: ${err.message}`);
|
|
802
767
|
});
|
|
803
768
|
} else if (cmd === 'nuke') {
|
|
804
769
|
ensureInstalled();
|
|
@@ -967,10 +932,7 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
967
932
|
handled = true;
|
|
968
933
|
const url = `http://localhost:${DASH_PORT}`;
|
|
969
934
|
console.log(`\n Dashboard already running: ${url}\n`);
|
|
970
|
-
|
|
971
|
-
const openCmd = process.platform === 'win32' ? `start ${url}` : process.platform === 'darwin' ? `open ${url}` : `xdg-open ${url}`;
|
|
972
|
-
execSync(openCmd, { stdio: 'ignore', windowsHide: true });
|
|
973
|
-
} catch {}
|
|
935
|
+
openUrlInBrowser(url);
|
|
974
936
|
});
|
|
975
937
|
sock.on('error', () => {
|
|
976
938
|
sock.destroy();
|
package/dashboard.js
CHANGED
|
@@ -9422,20 +9422,12 @@ if (require.main === module) {
|
|
|
9422
9422
|
console.log(`\n Auto-refreshes every 4s. Ctrl+C to stop.\n`);
|
|
9423
9423
|
|
|
9424
9424
|
// Auto-open the browser unless suppressed. `minions restart` and the
|
|
9425
|
-
// upgrade path set MINIONS_NO_AUTO_OPEN=1
|
|
9426
|
-
//
|
|
9425
|
+
// upgrade path set MINIONS_NO_AUTO_OPEN=1 because the CLI orchestrates the
|
|
9426
|
+
// open itself after observing whether an existing tab reconnected.
|
|
9427
9427
|
if (!process.env.MINIONS_NO_AUTO_OPEN) {
|
|
9428
|
-
const
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
exec(`start "" "http://localhost:${PORT}"`);
|
|
9432
|
-
} else if (process.platform === 'darwin') {
|
|
9433
|
-
exec(`open http://localhost:${PORT}`);
|
|
9434
|
-
} else {
|
|
9435
|
-
exec(`xdg-open http://localhost:${PORT}`);
|
|
9436
|
-
}
|
|
9437
|
-
} catch (e) {
|
|
9438
|
-
console.log(` Could not auto-open browser: ${e.message}`);
|
|
9428
|
+
const result = shared.openUrlInBrowser(`http://localhost:${PORT}`);
|
|
9429
|
+
if (!result.ok) {
|
|
9430
|
+
console.log(` Could not auto-open browser: ${result.error}`);
|
|
9439
9431
|
console.log(` Please open http://localhost:${PORT} manually.`);
|
|
9440
9432
|
}
|
|
9441
9433
|
}
|
package/engine/shared.js
CHANGED
|
@@ -251,6 +251,21 @@ function resolveEngineCacheDir(fallbackEngineDir) {
|
|
|
251
251
|
return fallbackEngineDir;
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
// Cross-platform URL opener. Uses execSync so failures fall through the
|
|
255
|
+
// try/catch and the caller sees them. Dashboard self-open and `minions dash`
|
|
256
|
+
// / `minions restart` post-health open all funnel through here.
|
|
257
|
+
function openUrlInBrowser(url) {
|
|
258
|
+
const { execSync } = require('child_process');
|
|
259
|
+
try {
|
|
260
|
+
if (process.platform === 'win32') execSync(`start "" "${url}"`, { stdio: 'ignore', windowsHide: true });
|
|
261
|
+
else if (process.platform === 'darwin') execSync(`open "${url}"`, { stdio: 'ignore' });
|
|
262
|
+
else execSync(`xdg-open "${url}"`, { stdio: 'ignore' });
|
|
263
|
+
return { ok: true };
|
|
264
|
+
} catch (e) {
|
|
265
|
+
return { ok: false, error: e && e.message ? e.message : String(e) };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
254
269
|
function _flushLogBuffer() {
|
|
255
270
|
if (_logBuffer.length === 0) return;
|
|
256
271
|
const drained = _logBuffer.splice(0);
|
|
@@ -3380,6 +3395,7 @@ module.exports = {
|
|
|
3380
3395
|
MINIONS_DIR,
|
|
3381
3396
|
ENGINE_DIR,
|
|
3382
3397
|
resolveEngineCacheDir,
|
|
3398
|
+
openUrlInBrowser,
|
|
3383
3399
|
CONTROL_PATH,
|
|
3384
3400
|
COOLDOWNS_PATH,
|
|
3385
3401
|
PR_LINKS_PATH,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1828",
|
|
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"
|