@yemi33/minions 0.1.1905 → 0.1.1907
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 +19 -3
- package/engine/copilot-models.json +5 -0
- package/engine/lifecycle.js +34 -8
- package/engine/restart-health.js +1 -1
- package/engine.js +17 -10
- package/package.json +1 -1
package/bin/minions.js
CHANGED
|
@@ -168,12 +168,26 @@ function killMinionsProcesses(patterns) {
|
|
|
168
168
|
} catch {}
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/** Open append-mode FDs under engine/ for detached-process stdio. If the file
|
|
172
|
+
* can't be opened we fall back to 'ignore' rather than blocking the restart. */
|
|
173
|
+
function _openStdioLog(name) {
|
|
174
|
+
try {
|
|
175
|
+
const dir = path.join(MINIONS_HOME, 'engine');
|
|
176
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
177
|
+
return fs.openSync(path.join(dir, name), 'a');
|
|
178
|
+
} catch { return 'ignore'; }
|
|
179
|
+
}
|
|
180
|
+
|
|
171
181
|
/** Spawn a detached dashboard with self-open suppressed — the CLI decides
|
|
172
|
-
* when to open a browser based on whether a real tab reconnects post-health.
|
|
182
|
+
* when to open a browser based on whether a real tab reconnects post-health.
|
|
183
|
+
* stdout/stderr land in engine/dashboard-stdio.log so silent startup crashes
|
|
184
|
+
* leave a postmortem instead of vanishing. */
|
|
173
185
|
function spawnDashboard() {
|
|
174
186
|
const env = { ...process.env, MINIONS_NO_AUTO_OPEN: '1' };
|
|
187
|
+
const out = _openStdioLog('dashboard-stdio.log');
|
|
188
|
+
const err = _openStdioLog('dashboard-stdio.log');
|
|
175
189
|
const proc = spawn(process.execPath, [path.join(MINIONS_HOME, 'dashboard.js')], {
|
|
176
|
-
cwd: MINIONS_HOME, stdio: 'ignore', detached: true, windowsHide: true, env
|
|
190
|
+
cwd: MINIONS_HOME, stdio: ['ignore', out, err], detached: true, windowsHide: true, env
|
|
177
191
|
});
|
|
178
192
|
proc.unref();
|
|
179
193
|
return proc;
|
|
@@ -736,8 +750,10 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
736
750
|
// Clear stale beacons AFTER the kill so the old dashboard's last writes
|
|
737
751
|
// can't repopulate the file in the gap between clear and shutdown.
|
|
738
752
|
_clearDashboardBrowserState(MINIONS_HOME);
|
|
753
|
+
const engineOut = _openStdioLog('engine-stdio.log');
|
|
754
|
+
const engineErr = _openStdioLog('engine-stdio.log');
|
|
739
755
|
const engineProc = spawn(process.execPath, [path.join(MINIONS_HOME, 'engine.js'), 'start', ...rest], {
|
|
740
|
-
cwd: MINIONS_HOME, stdio: 'ignore', detached: true, windowsHide: true
|
|
756
|
+
cwd: MINIONS_HOME, stdio: ['ignore', engineOut, engineErr], detached: true, windowsHide: true
|
|
741
757
|
});
|
|
742
758
|
engineProc.unref();
|
|
743
759
|
console.log(`\n Engine started (PID: ${engineProc.pid})`);
|
package/engine/lifecycle.js
CHANGED
|
@@ -1847,15 +1847,32 @@ function recordPrNoOpFixAttempt(target, cause, source, dispatchItem, branchChang
|
|
|
1847
1847
|
// filter can short-circuit duplicate build-fix dispatches against an
|
|
1848
1848
|
// unchanged commit. Reset happens implicitly when headSha advances and the
|
|
1849
1849
|
// discovery filter compares lastDispatchHeadSha to the current head.
|
|
1850
|
+
//
|
|
1851
|
+
// Tracking is partitioned by cause (`_lastDispatchByCause[cause]`) so a
|
|
1852
|
+
// human-feedback noop on head X never suppresses an unrelated build-failure
|
|
1853
|
+
// dispatch on head X (the PR-wide top-level fields would, see
|
|
1854
|
+
// W-mp2vohea00112739). The legacy top-level fields are kept in sync for
|
|
1855
|
+
// any external consumer (dashboards, debugging) but the build-fix
|
|
1856
|
+
// eligibility filter reads ONLY the per-cause map.
|
|
1850
1857
|
const headSha = getPrFixBaselineHead(target);
|
|
1851
|
-
|
|
1852
|
-
target.lastDispatchOutcome = 'noop';
|
|
1853
|
-
target.lastDispatchHeadSha = headSha;
|
|
1854
|
-
target.lastDispatchReason = String(
|
|
1858
|
+
const reasonText = String(
|
|
1855
1859
|
noopReason
|
|
1856
1860
|
|| branchChange?.reason
|
|
1857
1861
|
|| 'fix completed without changing the PR branch'
|
|
1858
1862
|
).slice(0, 500);
|
|
1863
|
+
target._lastDispatchByCause = target._lastDispatchByCause
|
|
1864
|
+
&& typeof target._lastDispatchByCause === 'object' ? target._lastDispatchByCause : {};
|
|
1865
|
+
target._lastDispatchByCause[cause] = {
|
|
1866
|
+
outcome: 'noop',
|
|
1867
|
+
headSha,
|
|
1868
|
+
reason: reasonText,
|
|
1869
|
+
dispatchedAt: now,
|
|
1870
|
+
dispatchId: dispatchItem?.id || null,
|
|
1871
|
+
};
|
|
1872
|
+
target.lastDispatchedAt = now;
|
|
1873
|
+
target.lastDispatchOutcome = 'noop';
|
|
1874
|
+
target.lastDispatchHeadSha = headSha;
|
|
1875
|
+
target.lastDispatchReason = reasonText;
|
|
1859
1876
|
|
|
1860
1877
|
if (cause === shared.PR_FIX_CAUSE.HUMAN_FEEDBACK && target.humanFeedback) {
|
|
1861
1878
|
target.humanFeedback.pendingFix = !paused;
|
|
@@ -1877,10 +1894,19 @@ function clearPrNoOpFixAttempt(target, cause) {
|
|
|
1877
1894
|
// the same head; once the agent actually pushed a fix we no longer want them
|
|
1878
1895
|
// to suppress a fresh dispatch (the SHA may have moved or the next failure
|
|
1879
1896
|
// is genuinely new).
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1897
|
+
if (target._lastDispatchByCause && typeof target._lastDispatchByCause === 'object') {
|
|
1898
|
+
delete target._lastDispatchByCause[cause];
|
|
1899
|
+
if (Object.keys(target._lastDispatchByCause).length === 0) delete target._lastDispatchByCause;
|
|
1900
|
+
}
|
|
1901
|
+
// Only clear the legacy top-level fields when no other cause still has a
|
|
1902
|
+
// tracked noop — otherwise a successful fix for cause A would silently wipe
|
|
1903
|
+
// out the same-head suppression record for unrelated cause B.
|
|
1904
|
+
if (!target._lastDispatchByCause) {
|
|
1905
|
+
delete target.lastDispatchedAt;
|
|
1906
|
+
delete target.lastDispatchOutcome;
|
|
1907
|
+
delete target.lastDispatchHeadSha;
|
|
1908
|
+
delete target.lastDispatchReason;
|
|
1909
|
+
}
|
|
1884
1910
|
}
|
|
1885
1911
|
|
|
1886
1912
|
function updatePrAfterFix(pr, project, source, options = {}, legacyDispatchId = '') {
|
package/engine/restart-health.js
CHANGED
|
@@ -92,7 +92,7 @@ async function checkRestartHealth(options = {}) {
|
|
|
92
92
|
const engineAlive = pid ? isAlive(pid) : false;
|
|
93
93
|
const engineOk = control && control.state === 'running' && engineAlive;
|
|
94
94
|
|
|
95
|
-
const dashboard = await getJson(dashboardUrl,
|
|
95
|
+
const dashboard = await getJson(dashboardUrl, 3000);
|
|
96
96
|
const dashboardStatus = dashboard && dashboard.json && dashboard.json.status;
|
|
97
97
|
const dashboardOk = !!(dashboard && dashboard.ok && dashboardStatus === 'healthy');
|
|
98
98
|
|
package/engine.js
CHANGED
|
@@ -2971,18 +2971,25 @@ async function discoverFromPrs(config, project) {
|
|
|
2971
2971
|
const autoFixBuilds = config.engine?.autoFixBuilds ?? ENGINE_DEFAULTS.autoFixBuilds;
|
|
2972
2972
|
if (pollEnabled && autoFixBuilds && pr.status === PR_STATUS.ACTIVE && pr.buildStatus === 'failing'
|
|
2973
2973
|
&& !isPrNoOpFixCauseSuppressed(pr, shared.PR_FIX_CAUSE.BUILD_FAILURE)) {
|
|
2974
|
-
// P-b7e1c4d2: skip when the most recent dispatch already
|
|
2975
|
-
// the same head SHA — chronic across PRs #2315–#2323
|
|
2976
|
-
// agent rebutted "this is a pre-existing master baseline"
|
|
2977
|
-
// cached buildStatus:failing kept re-triggering the loop. The
|
|
2978
|
-
// clears automatically once a new commit lands (
|
|
2979
|
-
// stops matching the current head).
|
|
2974
|
+
// P-b7e1c4d2: skip when the most recent BUILD-FAILURE dispatch already
|
|
2975
|
+
// noop'd against the same head SHA — chronic across PRs #2315–#2323
|
|
2976
|
+
// where every fix agent rebutted "this is a pre-existing master baseline"
|
|
2977
|
+
// but the cached buildStatus:failing kept re-triggering the loop. The
|
|
2978
|
+
// check clears automatically once a new commit lands (the per-cause
|
|
2979
|
+
// headSha stops matching the current head).
|
|
2980
|
+
//
|
|
2981
|
+
// W-mp2vohea00112739: this guard reads `_lastDispatchByCause['build-failure']`
|
|
2982
|
+
// instead of the PR-wide `lastDispatch*` fields. The PR-wide fields are
|
|
2983
|
+
// shared across all causes, so a `human-feedback` noop on head X used to
|
|
2984
|
+
// suppress an unrelated `build-failure` dispatch on the same head until
|
|
2985
|
+
// a new commit landed (live repro on PR #2433).
|
|
2980
2986
|
const currentHeadSha = String(pr.headSha || pr._adoSourceCommit || pr._adoHeadCommit || '').trim();
|
|
2981
|
-
|
|
2982
|
-
|
|
2987
|
+
const lastBuildDispatch = pr._lastDispatchByCause?.[shared.PR_FIX_CAUSE.BUILD_FAILURE];
|
|
2988
|
+
if (lastBuildDispatch?.outcome === 'noop'
|
|
2989
|
+
&& lastBuildDispatch.headSha
|
|
2983
2990
|
&& currentHeadSha
|
|
2984
|
-
&&
|
|
2985
|
-
log('info', `Skipping build-fix for ${pr.id}: last dispatch was noop on the same head ${currentHeadSha.slice(0, 8)} (${(
|
|
2991
|
+
&& lastBuildDispatch.headSha === currentHeadSha) {
|
|
2992
|
+
log('info', `Skipping build-fix for ${pr.id}: last build-failure dispatch was noop on the same head ${currentHeadSha.slice(0, 8)} (${(lastBuildDispatch.reason || '').slice(0, 120)})`);
|
|
2986
2993
|
continue;
|
|
2987
2994
|
}
|
|
2988
2995
|
const buildCauseKey = getPrAutomationCauseKey('build', pr);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1907",
|
|
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"
|