@yemi33/minions 0.1.549 → 0.1.551
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 +7 -1
- package/dashboard/js/render-prs.js +2 -2
- package/dashboard/styles.css +1 -0
- package/dashboard.js +7 -6
- package/engine/ado.js +34 -0
- package/engine.js +3 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.551 (2026-04-07)
|
|
4
4
|
|
|
5
5
|
### Fixes
|
|
6
|
+
- mark stale build status on ADO auth failure, bypass 6-tick cadence for recovery (#483)
|
|
7
|
+
|
|
8
|
+
## 0.1.550 (2026-04-07)
|
|
9
|
+
|
|
10
|
+
### Fixes
|
|
11
|
+
- use isGitRepo flag for update banner instead of diskCommit presence
|
|
6
12
|
- prefer git rev-parse over .minions-commit for disk version detection
|
|
7
13
|
|
|
8
14
|
## 0.1.548 (2026-04-07)
|
|
@@ -12,8 +12,8 @@ function prRow(pr) {
|
|
|
12
12
|
const reviewSource = sq.status || effectiveReviewStatus || 'pending';
|
|
13
13
|
const reviewClass = reviewSource === 'approved' ? 'approved' : (reviewSource === 'changes-requested' || reviewSource === 'rejected') ? 'rejected' : reviewSource === 'waiting' ? 'building' : 'draft';
|
|
14
14
|
const reviewLabel = sq.status === 'waiting' ? 'reviewing (minions)' : sq.status ? sq.status + ' (minions)' : (effectiveReviewStatus || 'pending');
|
|
15
|
-
const buildClass = pr.buildStatus === 'passing' ? 'build-pass' : pr.buildStatus === 'failing' ? 'build-fail' : pr.buildStatus === 'running' ? 'building' : 'no-build';
|
|
16
|
-
const buildLabel = pr.buildStatus || 'none';
|
|
15
|
+
const buildClass = pr._buildStatusStale ? 'build-stale' : pr.buildStatus === 'passing' ? 'build-pass' : pr.buildStatus === 'failing' ? 'build-fail' : pr.buildStatus === 'running' ? 'building' : 'no-build';
|
|
16
|
+
const buildLabel = (pr.buildStatus || 'none') + (pr._buildStatusStale ? ' (stale)' : '');
|
|
17
17
|
const statusClass = pr.status === 'merged' ? 'merged' : pr.status === 'abandoned' ? 'rejected' : pr.status === 'active' ? 'active' : 'draft';
|
|
18
18
|
const statusLabel = pr.status || 'active';
|
|
19
19
|
const url = pr.url || '#';
|
package/dashboard/styles.css
CHANGED
|
@@ -251,6 +251,7 @@
|
|
|
251
251
|
.pr-badge.build-pass { background: rgba(63,185,80,0.15); color: var(--green); border: 1px solid var(--green); }
|
|
252
252
|
.pr-badge.build-fail { background: rgba(248,81,73,0.15); color: var(--red); border: 1px solid var(--red); }
|
|
253
253
|
.pr-badge.no-build { background: var(--surface); color: var(--muted); border: 1px solid var(--border); }
|
|
254
|
+
.pr-badge.build-stale { background: rgba(210,153,34,0.15); color: var(--orange); border: 1px dashed var(--orange); }
|
|
254
255
|
.error-details-btn { font-size: var(--text-xs); padding: var(--space-1) var(--space-3); margin-left: var(--space-2); background: rgba(248,81,73,0.15); color: var(--red); border: 1px solid var(--red); border-radius: var(--radius-lg); cursor: pointer; font-weight: 600; text-transform: uppercase; letter-spacing: 0.3px; }
|
|
255
256
|
.error-details-btn:hover { background: rgba(248,81,73,0.3); }
|
|
256
257
|
.pr-empty { color: var(--muted); font-style: italic; font-size: var(--text-md); padding: var(--space-6) 0; }
|
package/dashboard.js
CHANGED
|
@@ -230,12 +230,13 @@ function getDiskVersion() {
|
|
|
230
230
|
try { diskVersion = require('@yemi33/minions/package.json').version; } catch {}
|
|
231
231
|
}
|
|
232
232
|
let diskCommit = null;
|
|
233
|
+
let isGitRepo = false;
|
|
233
234
|
// Prefer git (authoritative for repo-based dev), fall back to .minions-commit (installed copies)
|
|
234
|
-
try { diskCommit = require('child_process').execSync('git rev-parse --short HEAD', { cwd: MINIONS_DIR, encoding: 'utf8', timeout: 5000, windowsHide: true }).trim(); } catch {}
|
|
235
|
+
try { diskCommit = require('child_process').execSync('git rev-parse --short HEAD', { cwd: MINIONS_DIR, encoding: 'utf8', timeout: 5000, windowsHide: true }).trim(); isGitRepo = true; } catch {}
|
|
235
236
|
if (!diskCommit) {
|
|
236
237
|
try { diskCommit = fs.readFileSync(path.join(MINIONS_DIR, '.minions-commit'), 'utf8').trim() || null; } catch {}
|
|
237
238
|
}
|
|
238
|
-
_diskVersionCache = { diskVersion, diskCommit };
|
|
239
|
+
_diskVersionCache = { diskVersion, diskCommit, isGitRepo };
|
|
239
240
|
_diskVersionCacheTs = now;
|
|
240
241
|
return _diskVersionCache;
|
|
241
242
|
}
|
|
@@ -372,7 +373,7 @@ function getStatus() {
|
|
|
372
373
|
installId: safeRead(path.join(MINIONS_DIR, '.install-id')).trim() || null,
|
|
373
374
|
version: (() => {
|
|
374
375
|
const engine = getEngineState();
|
|
375
|
-
const { diskVersion, diskCommit } = getDiskVersion();
|
|
376
|
+
const { diskVersion, diskCommit, isGitRepo } = getDiskVersion();
|
|
376
377
|
const engineStale = !!(engine.codeVersion && diskVersion && engine.codeVersion !== diskVersion) ||
|
|
377
378
|
!!(engine.codeCommit && diskCommit && engine.codeCommit !== diskCommit);
|
|
378
379
|
const dashboardStale = !!(diskVersion && _dashboardVersion.codeVersion && diskVersion !== _dashboardVersion.codeVersion) ||
|
|
@@ -390,7 +391,7 @@ function getStatus() {
|
|
|
390
391
|
stale: engineStale || dashboardStale,
|
|
391
392
|
latest: _npmVersionCache?.latest || null,
|
|
392
393
|
// Only show "update available" for npm installs (no git repo) — repo users manage their own updates
|
|
393
|
-
updateAvailable: !
|
|
394
|
+
updateAvailable: !isGitRepo && !!(diskVersion && _npmVersionCache?.latest && _npmVersionCache.latest !== diskVersion && _compareVersions(_npmVersionCache.latest, diskVersion) > 0),
|
|
394
395
|
_npmCheckError: _npmVersionCache?.error || null,
|
|
395
396
|
};
|
|
396
397
|
})(),
|
|
@@ -3658,7 +3659,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3658
3659
|
// Routes endpoint (self-describing API)
|
|
3659
3660
|
{ method: 'GET', path: '/api/version', desc: 'Current + latest version info with update check', handler: async (req, res) => {
|
|
3660
3661
|
const npm = await checkNpmVersion();
|
|
3661
|
-
const { diskVersion, diskCommit } = getDiskVersion();
|
|
3662
|
+
const { diskVersion, diskCommit, isGitRepo } = getDiskVersion();
|
|
3662
3663
|
const engine = getEngineState();
|
|
3663
3664
|
const engineStale = !!(engine.codeVersion && diskVersion && engine.codeVersion !== diskVersion) ||
|
|
3664
3665
|
!!(engine.codeCommit && diskCommit && engine.codeCommit !== diskCommit);
|
|
@@ -3672,7 +3673,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3672
3673
|
dashboardRunning: _dashboardVersion.codeVersion,
|
|
3673
3674
|
dashboardRunningCommit: _dashboardVersion.codeCommit,
|
|
3674
3675
|
latest: npm.latest,
|
|
3675
|
-
updateAvailable: !
|
|
3676
|
+
updateAvailable: !isGitRepo && !!(diskVersion && npm.latest && _compareVersions(npm.latest, diskVersion) > 0),
|
|
3676
3677
|
engineStale,
|
|
3677
3678
|
dashboardStale,
|
|
3678
3679
|
stale: engineStale || dashboardStale,
|
package/engine/ado.js
CHANGED
|
@@ -21,6 +21,20 @@ function engine() {
|
|
|
21
21
|
let _adoTokenCache = { token: null, expiresAt: 0 };
|
|
22
22
|
let _adoTokenFailedUntil = 0; // backoff: skip azureauth calls until this timestamp
|
|
23
23
|
|
|
24
|
+
// ─── Auth Failure Tracking ──────────────────────────────────────────────────
|
|
25
|
+
// Set when pollPrStatus encounters auth errors mid-loop. The engine checks this
|
|
26
|
+
// to bypass the normal 6-tick cadence and re-poll on the next tick.
|
|
27
|
+
let _adoPollHadAuthFailure = false;
|
|
28
|
+
|
|
29
|
+
/** Check if auth failure during PR poll means an early re-poll is needed. */
|
|
30
|
+
function needsAdoPollRetry() { return _adoPollHadAuthFailure; }
|
|
31
|
+
|
|
32
|
+
/** Detect auth-related errors from adoFetch (HTML redirect, 401, 403). */
|
|
33
|
+
function isAdoAuthError(err) {
|
|
34
|
+
const msg = err?.message || '';
|
|
35
|
+
return msg.includes('auth redirect') || msg.includes('HTML instead of JSON') || /ADO API (401|403)/.test(msg);
|
|
36
|
+
}
|
|
37
|
+
|
|
24
38
|
async function getAdoToken() {
|
|
25
39
|
if (_adoTokenCache.token && Date.now() < _adoTokenCache.expiresAt) {
|
|
26
40
|
return _adoTokenCache.token;
|
|
@@ -133,16 +147,23 @@ async function forEachActivePr(config, token, callback) {
|
|
|
133
147
|
// ─── PR Status Polling ───────────────────────────────────────────────────────
|
|
134
148
|
|
|
135
149
|
async function pollPrStatus(config) {
|
|
150
|
+
_adoPollHadAuthFailure = false; // reset before polling — set again if errors recur
|
|
151
|
+
|
|
136
152
|
const token = await getAdoToken();
|
|
137
153
|
if (!token) {
|
|
138
154
|
log('warn', 'Skipping PR status poll — no ADO token available');
|
|
155
|
+
_adoPollHadAuthFailure = true; // trigger retry on next tick
|
|
139
156
|
return;
|
|
140
157
|
}
|
|
141
158
|
|
|
142
159
|
const totalUpdated = await forEachActivePr(config, token, async (project, pr, prNum, orgBase) => {
|
|
160
|
+
try {
|
|
143
161
|
const repoBase = `${orgBase}/${project.adoProject}/_apis/git/repositories/${project.repositoryId}/pullrequests/${prNum}`;
|
|
144
162
|
let updated = false;
|
|
145
163
|
|
|
164
|
+
// Clear stale flag — we're attempting a fresh poll
|
|
165
|
+
if (pr._buildStatusStale) { delete pr._buildStatusStale; updated = true; }
|
|
166
|
+
|
|
146
167
|
const prData = await adoFetch(`${repoBase}?api-version=7.1`, token);
|
|
147
168
|
|
|
148
169
|
let newStatus = pr.status;
|
|
@@ -278,6 +299,17 @@ async function pollPrStatus(config) {
|
|
|
278
299
|
}
|
|
279
300
|
|
|
280
301
|
return updated;
|
|
302
|
+
} catch (err) {
|
|
303
|
+
// Auth errors → mark build status stale so dashboard shows uncertainty
|
|
304
|
+
// and engine re-polls on next tick instead of waiting 6 ticks
|
|
305
|
+
if (isAdoAuthError(err)) {
|
|
306
|
+
pr._buildStatusStale = true;
|
|
307
|
+
_adoPollHadAuthFailure = true;
|
|
308
|
+
log('warn', `PR ${pr.id}: build status marked stale (auth error: ${err.message})`);
|
|
309
|
+
return true; // count as updated to persist the stale flag
|
|
310
|
+
}
|
|
311
|
+
throw err; // re-throw non-auth errors for forEachActivePr to handle
|
|
312
|
+
}
|
|
281
313
|
});
|
|
282
314
|
|
|
283
315
|
if (totalUpdated > 0) {
|
|
@@ -520,5 +552,7 @@ module.exports = {
|
|
|
520
552
|
pollPrHumanComments,
|
|
521
553
|
reconcilePrs,
|
|
522
554
|
checkLiveReviewStatus,
|
|
555
|
+
needsAdoPollRetry,
|
|
556
|
+
isAdoAuthError, // exported for testing
|
|
523
557
|
};
|
|
524
558
|
|
package/engine.js
CHANGED
|
@@ -937,7 +937,7 @@ function reconcileItemsWithPrs(items, allPrs, { onlyIds } = {}) {
|
|
|
937
937
|
// ─── Inbox Consolidation (extracted to engine/consolidation.js) ──────────────
|
|
938
938
|
|
|
939
939
|
const { consolidateInbox } = require('./engine/consolidation');
|
|
940
|
-
const { pollPrStatus, pollPrHumanComments, reconcilePrs, checkLiveReviewStatus: adoCheckLiveReview } = require('./engine/ado');
|
|
940
|
+
const { pollPrStatus, pollPrHumanComments, reconcilePrs, checkLiveReviewStatus: adoCheckLiveReview, needsAdoPollRetry } = require('./engine/ado');
|
|
941
941
|
const { pollPrStatus: ghPollPrStatus, pollPrHumanComments: ghPollPrHumanComments, reconcilePrs: ghReconcilePrs, checkLiveReviewStatus: ghCheckLiveReview } = require('./engine/github');
|
|
942
942
|
|
|
943
943
|
// ─── State Snapshot ─────────────────────────────────────────────────────────
|
|
@@ -2447,7 +2447,8 @@ async function tickInner() {
|
|
|
2447
2447
|
|
|
2448
2448
|
// 2.6. Poll PR status: build, review, merge (every 6 ticks = ~3 minutes)
|
|
2449
2449
|
// Awaited so PR state is consistent before discoverWork reads it
|
|
2450
|
-
if (
|
|
2450
|
+
// Also re-polls early if previous tick had ADO auth failures (stale build status recovery)
|
|
2451
|
+
if (tickCount % 6 === 0 || needsAdoPollRetry()) {
|
|
2451
2452
|
try { await pollPrStatus(config); } catch (err) { log('warn', `ADO PR status poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
|
|
2452
2453
|
try { await ghPollPrStatus(config); } catch (err) { log('warn', `GitHub PR status poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
|
|
2453
2454
|
// Sync PR status back to PRD items (missing → done when active PR exists)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.551",
|
|
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"
|