@yemi33/minions 0.1.1825 → 0.1.1827
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 +68 -38
- 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.1827 (2026-05-09)
|
|
4
|
+
|
|
5
|
+
### Other
|
|
6
|
+
- refactor(cli): consolidate browser-open helpers and tighten restart flow
|
|
7
|
+
|
|
8
|
+
## 0.1.1826 (2026-05-09)
|
|
9
|
+
|
|
10
|
+
### Fixes
|
|
11
|
+
- always open dashboard if no live tab reconnects after restart
|
|
12
|
+
|
|
3
13
|
## 0.1.1825 (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,16 +74,37 @@ function killByPort(port) {
|
|
|
74
74
|
|
|
75
75
|
const isPortListening = (port) => getListeningPids(port).length > 0;
|
|
76
76
|
|
|
77
|
-
|
|
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.
|
|
81
|
+
function _clearDashboardBrowserState(minionsHome) {
|
|
78
82
|
try {
|
|
79
83
|
const fp = path.join(minionsHome, 'engine', 'dashboard-browser.json');
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
fs.writeFileSync(fp, JSON.stringify({ tabs: {}, updatedAt: new Date().toISOString() }));
|
|
85
|
+
} catch {}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function _waitForBrowserReconnect(minionsHome, { afterMs, timeoutMs = 5000, pollMs = 500 } = {}) {
|
|
89
|
+
const start = Date.now();
|
|
90
|
+
while (Date.now() - start < timeoutMs) {
|
|
91
|
+
try {
|
|
92
|
+
const fp = path.join(minionsHome, 'engine', 'dashboard-browser.json');
|
|
93
|
+
const state = JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
94
|
+
const tabs = state && state.tabs ? Object.values(state.tabs) : [];
|
|
95
|
+
if (tabs.some(t => Number(t && t.lastSeen) > afterMs)) return true;
|
|
96
|
+
} catch { /* file may be temporarily missing during write */ }
|
|
97
|
+
await new Promise(r => setTimeout(r, pollMs));
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function _openInBrowser(url) {
|
|
103
|
+
const result = openUrlInBrowser(url);
|
|
104
|
+
if (!result.ok) {
|
|
105
|
+
console.log(` Could not auto-open browser: ${result.error}`);
|
|
106
|
+
console.log(` Please open ${url} manually.`);
|
|
107
|
+
}
|
|
87
108
|
}
|
|
88
109
|
|
|
89
110
|
/**
|
|
@@ -143,12 +164,10 @@ function killMinionsProcesses(patterns) {
|
|
|
143
164
|
} catch {}
|
|
144
165
|
}
|
|
145
166
|
|
|
146
|
-
/** Spawn a detached dashboard
|
|
147
|
-
*
|
|
148
|
-
function spawnDashboard(
|
|
149
|
-
const env = { ...process.env };
|
|
150
|
-
if (suppressOpen) env.MINIONS_NO_AUTO_OPEN = '1';
|
|
151
|
-
else delete env.MINIONS_NO_AUTO_OPEN;
|
|
167
|
+
/** Spawn a detached dashboard with self-open suppressed — the CLI decides
|
|
168
|
+
* when to open a browser based on whether a real tab reconnects post-health. */
|
|
169
|
+
function spawnDashboard() {
|
|
170
|
+
const env = { ...process.env, MINIONS_NO_AUTO_OPEN: '1' };
|
|
152
171
|
const proc = spawn(process.execPath, [path.join(MINIONS_HOME, 'dashboard.js')], {
|
|
153
172
|
cwd: MINIONS_HOME, stdio: 'ignore', detached: true, windowsHide: true, env
|
|
154
173
|
});
|
|
@@ -411,16 +430,15 @@ function init() {
|
|
|
411
430
|
if (isUpgrade && skipStart) return;
|
|
412
431
|
|
|
413
432
|
// Auto-start on fresh install; direct force-upgrade restarts automatically.
|
|
414
|
-
// Probe before kill so we suppress browser auto-open only when a browser tab
|
|
415
|
-
// was recently polling the dashboard. A bare/orphan dashboard process on the
|
|
416
|
-
// port is not enough; cold starts should still open the UI.
|
|
417
433
|
const dashWasUp = isPortListening(DASH_PORT);
|
|
418
|
-
const
|
|
434
|
+
const restartStartMs = Date.now();
|
|
419
435
|
if (isUpgrade) {
|
|
420
436
|
try { execSync(`node "${path.join(MINIONS_HOME, 'engine.js')}" stop`, { stdio: 'ignore', cwd: MINIONS_HOME, timeout: 10000, windowsHide: true }); } catch {}
|
|
421
437
|
// Free the dashboard port too — without this the new dashboard EADDRINUSE-dies
|
|
422
438
|
// silently and the user keeps running stale code from the old dashboard process.
|
|
423
439
|
killByPort(DASH_PORT);
|
|
440
|
+
// Clear AFTER kill so the old dashboard can't repopulate during shutdown.
|
|
441
|
+
_clearDashboardBrowserState(MINIONS_HOME);
|
|
424
442
|
}
|
|
425
443
|
console.log(isUpgrade
|
|
426
444
|
? `\n Upgrade complete (${pkgVersion}). Restarting engine and dashboard...\n`
|
|
@@ -431,10 +449,21 @@ function init() {
|
|
|
431
449
|
engineProc.unref();
|
|
432
450
|
console.log(` Engine started (PID: ${engineProc.pid})`);
|
|
433
451
|
|
|
434
|
-
const dashProc = spawnDashboard(
|
|
452
|
+
const dashProc = spawnDashboard();
|
|
435
453
|
console.log(` Dashboard started (PID: ${dashProc.pid})`);
|
|
436
454
|
console.log(` Dashboard: http://localhost:${DASH_PORT}`);
|
|
437
455
|
|
|
456
|
+
void (async () => {
|
|
457
|
+
const shouldOpen = forceOpen || !dashWasUp ||
|
|
458
|
+
!(await _waitForBrowserReconnect(MINIONS_HOME, { afterMs: restartStartMs, timeoutMs: 5000 }));
|
|
459
|
+
if (shouldOpen) {
|
|
460
|
+
console.log(` Opening dashboard in browser...`);
|
|
461
|
+
_openInBrowser(`http://localhost:${DASH_PORT}`);
|
|
462
|
+
}
|
|
463
|
+
})().catch(err => {
|
|
464
|
+
console.log(` Could not open dashboard: ${err.message}`);
|
|
465
|
+
});
|
|
466
|
+
|
|
438
467
|
// Next steps guidance
|
|
439
468
|
console.log(`
|
|
440
469
|
Next steps:
|
|
@@ -685,34 +714,30 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
685
714
|
} else if (cmd === 'add' || cmd === 'remove' || cmd === 'list' || cmd === 'scan') {
|
|
686
715
|
delegate('minions.js', [cmd, ...rest]);
|
|
687
716
|
} else if (cmd === 'restart') {
|
|
688
|
-
// Start both engine and dashboard — the go-to command after a reboot.
|
|
689
717
|
// `--cli` / `--model` flags forward to `engine.js start` so the runtime
|
|
690
718
|
// fleet flips before the daemon spawns (P-6b3f9c2e AC: works on restart).
|
|
691
719
|
ensureInstalled();
|
|
692
|
-
// Probe before kill so we suppress browser auto-open only when a browser tab
|
|
693
|
-
// was recently polling the dashboard. A bare/orphan dashboard process on the
|
|
694
|
-
// port is not enough; cold starts should still open the UI.
|
|
695
720
|
const dashWasUp = isPortListening(DASH_PORT);
|
|
696
|
-
const
|
|
697
|
-
// Layered kill — each step is best-effort,
|
|
698
|
-
//
|
|
721
|
+
const restartStartMs = Date.now();
|
|
722
|
+
// Layered kill — each step is best-effort, so the next still runs if one
|
|
723
|
+
// fails. Goal: the old engine is gone before we spawn a new one, even if
|
|
699
724
|
// PowerShell is unavailable, the engine is hung, or its cmdline doesn't match.
|
|
700
725
|
const oldEnginePid = readEnginePid(MINIONS_HOME);
|
|
701
|
-
// 1. Graceful stop — short timeout so a hung engine can't block what follows.
|
|
702
726
|
try { execSync(`node "${path.join(MINIONS_HOME, 'engine.js')}" stop`, { stdio: 'ignore', cwd: MINIONS_HOME, timeout: 10000, windowsHide: true }); } catch {}
|
|
703
|
-
//
|
|
704
|
-
//
|
|
727
|
+
// Force-kill the recorded engine PID (NOT the tree — agent children must
|
|
728
|
+
// survive so the new engine can re-attach them via PID files).
|
|
705
729
|
killPidOnly(oldEnginePid);
|
|
706
|
-
// 3. Free dashboard port (catches orphan dashboards with no recorded PID).
|
|
707
730
|
killByPort(DASH_PORT);
|
|
708
|
-
// 4. Belt-and-suspenders cmdline match for anything still alive.
|
|
709
731
|
killMinionsProcesses(['engine.js', 'dashboard.js']);
|
|
732
|
+
// Clear stale beacons AFTER the kill so the old dashboard's last writes
|
|
733
|
+
// can't repopulate the file in the gap between clear and shutdown.
|
|
734
|
+
_clearDashboardBrowserState(MINIONS_HOME);
|
|
710
735
|
const engineProc = spawn(process.execPath, [path.join(MINIONS_HOME, 'engine.js'), 'start', ...rest], {
|
|
711
736
|
cwd: MINIONS_HOME, stdio: 'ignore', detached: true, windowsHide: true
|
|
712
737
|
});
|
|
713
738
|
engineProc.unref();
|
|
714
739
|
console.log(`\n Engine started (PID: ${engineProc.pid})`);
|
|
715
|
-
const dashProc = spawnDashboard(
|
|
740
|
+
const dashProc = spawnDashboard();
|
|
716
741
|
console.log(` Dashboard started (PID: ${dashProc.pid})`);
|
|
717
742
|
console.log(` Dashboard: http://localhost:${DASH_PORT}`);
|
|
718
743
|
console.log(' Verifying restart health...');
|
|
@@ -725,7 +750,15 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
725
750
|
console.error(formatRestartHealthError(result));
|
|
726
751
|
process.exit(1);
|
|
727
752
|
}
|
|
728
|
-
console.log(` Restart verified: engine PID ${result.engine.pid}; dashboard healthy
|
|
753
|
+
console.log(` Restart verified: engine PID ${result.engine.pid}; dashboard healthy.`);
|
|
754
|
+
|
|
755
|
+
const shouldOpen = forceOpen || !dashWasUp ||
|
|
756
|
+
!(await _waitForBrowserReconnect(MINIONS_HOME, { afterMs: restartStartMs, timeoutMs: 5000 }));
|
|
757
|
+
if (shouldOpen) {
|
|
758
|
+
console.log(` Opening dashboard in browser...`);
|
|
759
|
+
_openInBrowser(`http://localhost:${DASH_PORT}`);
|
|
760
|
+
}
|
|
761
|
+
console.log('');
|
|
729
762
|
})().catch(err => {
|
|
730
763
|
console.error(`\n ERROR: Restart verification failed: ${err.message}\n`);
|
|
731
764
|
process.exit(1);
|
|
@@ -897,10 +930,7 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
897
930
|
handled = true;
|
|
898
931
|
const url = `http://localhost:${DASH_PORT}`;
|
|
899
932
|
console.log(`\n Dashboard already running: ${url}\n`);
|
|
900
|
-
|
|
901
|
-
const openCmd = process.platform === 'win32' ? `start ${url}` : process.platform === 'darwin' ? `open ${url}` : `xdg-open ${url}`;
|
|
902
|
-
execSync(openCmd, { stdio: 'ignore', windowsHide: true });
|
|
903
|
-
} catch {}
|
|
933
|
+
openUrlInBrowser(url);
|
|
904
934
|
});
|
|
905
935
|
sock.on('error', () => {
|
|
906
936
|
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.1827",
|
|
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"
|