@yemi33/minions 0.1.550 → 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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.551 (2026-04-07)
4
+
5
+ ### Fixes
6
+ - mark stale build status on ADO auth failure, bypass 6-tick cadence for recovery (#483)
7
+
3
8
  ## 0.1.550 (2026-04-07)
4
9
 
5
10
  ### Fixes
@@ -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 || '#';
@@ -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/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 (tickCount % 6 === 0) {
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.550",
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"