@yemi33/minions 0.1.954 → 0.1.956

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,8 +1,10 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.954 (2026-04-14)
3
+ ## 0.1.956 (2026-04-14)
4
4
 
5
5
  ### Features
6
+ - wire agentBusyReassignMs into settings UI and persist
7
+ - ADO throttle detection, poll guards, and dashboard banner (#1051)
6
8
  - gate auto-fix conflict dispatch behind autoFixConflicts flag
7
9
  - add PR/build status CLI shim and agent guidance
8
10
  - make ADO poll frequency configurable and ungate reconcilePrs
@@ -21,8 +23,6 @@
21
23
  - fix dashboard plan-pause nested lock violation (#968)
22
24
  - add missing branch_name to central dispatch vars (#967)
23
25
  - fix dispatch.js mutator fallback to use nullish coalescing (#966)
24
- - fix stall recovery nested lock violation (#965)
25
- - fix pending-rebases.json race condition with file locking (#964)
26
26
 
27
27
  ### Fixes
28
28
  - fix ENGINE_DEFAULTS ref and derive boolean settings from defaults
@@ -75,6 +75,8 @@ function _processStatusUpdate(data) {
75
75
  }
76
76
  }
77
77
  if (_changed('version', data.version)) renderVersionBanner(data.version);
78
+ if (_changed('adoThrottle', data.adoThrottle)) renderAdoThrottleAlert(data.adoThrottle);
79
+ if (_changed('ghThrottle', data.ghThrottle)) renderGhThrottleAlert(data.ghThrottle);
78
80
  if (_changed('dispatch', data.dispatch)) renderDispatch(data.dispatch);
79
81
  window._lastDispatch = data.dispatch;
80
82
  window._lastWorkItems = data.workItems || [];
@@ -72,6 +72,34 @@ function renderEngineAlert(state, staleMs) {
72
72
  el.style.display = 'flex';
73
73
  }
74
74
 
75
+ function renderAdoThrottleAlert(adoThrottle) {
76
+ const el = document.getElementById('ado-throttle-alert');
77
+ if (!el) return;
78
+ if (!adoThrottle || !adoThrottle.throttled) {
79
+ el.style.display = 'none';
80
+ el.innerHTML = '';
81
+ return;
82
+ }
83
+ const resumeTime = new Date(adoThrottle.retryAfter).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
84
+ el.innerHTML =
85
+ '<span class="engine-alert-msg">&#x26A0;&#xFE0F; ADO rate-limited — resume ~' + resumeTime + ' (' + adoThrottle.consecutiveHits + ' consecutive hit' + (adoThrottle.consecutiveHits !== 1 ? 's' : '') + ')</span>';
86
+ el.style.display = 'flex';
87
+ }
88
+
89
+ function renderGhThrottleAlert(ghThrottle) {
90
+ const el = document.getElementById('gh-throttle-alert');
91
+ if (!el) return;
92
+ if (!ghThrottle || !ghThrottle.throttled) {
93
+ el.style.display = 'none';
94
+ el.innerHTML = '';
95
+ return;
96
+ }
97
+ const resumeTime = new Date(ghThrottle.retryAfter).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
98
+ el.innerHTML =
99
+ '<span class="engine-alert-msg">&#x26A0;&#xFE0F; GitHub rate-limited — resume ~' + resumeTime + ' (' + ghThrottle.consecutiveHits + ' consecutive hit' + (ghThrottle.consecutiveHits !== 1 ? 's' : '') + ')</span>';
100
+ el.style.display = 'flex';
101
+ }
102
+
75
103
  function renderDispatch(dispatch) {
76
104
  if (!dispatch) return;
77
105
 
@@ -247,4 +275,4 @@ function renderVersionBanner(version) {
247
275
  }
248
276
  }
249
277
 
250
- window.MinionsDispatch = { renderEngineStatus, renderEngineAlert, renderVersionBanner, renderDispatch, renderEngineLog, shortTime, showErrorDetails };
278
+ window.MinionsDispatch = { renderEngineStatus, renderEngineAlert, renderAdoThrottleAlert, renderGhThrottleAlert, renderVersionBanner, renderDispatch, renderEngineLog, shortTime, showErrorDetails };
@@ -61,6 +61,7 @@ async function openSettings() {
61
61
  settingsField('Eval Max Iterations', 'set-evalMaxIterations', e.evalMaxIterations || 3, '', 'Max review→fix cycles before escalating (1-10)') +
62
62
  settingsField('Eval Max Cost', 'set-evalMaxCost', e.evalMaxCost === null || e.evalMaxCost === undefined ? '' : e.evalMaxCost, '$', 'USD ceiling per work item across all eval iterations (blank = no limit)') +
63
63
  settingsField('Max Build Fix Attempts', 'set-maxBuildFixAttempts', e.maxBuildFixAttempts || 3, '', 'Max auto-fix dispatches per PR before escalating to human (1-10)') +
64
+ settingsField('Agent Busy Reassign', 'set-agentBusyReassignMs', e.agentBusyReassignMs || 600000, 'ms', 'Reassign work to another agent after it waits this long on a busy agent') +
64
65
  settingsField('Version Check Interval', 'set-versionCheckInterval', e.versionCheckInterval || 3600000, 'ms', 'How often to check npm for updates (default: 1 hour)') +
65
66
  settingsField('Ignored Comment Authors', 'set-ignoredCommentAuthors', (e.ignoredCommentAuthors || []).join(', '), '', 'Comma-separated usernames — comments auto-closed, never trigger fixes') +
66
67
  '</div>' +
@@ -246,6 +247,7 @@ async function saveSettings() {
246
247
  evalMaxIterations: document.getElementById('set-evalMaxIterations').value,
247
248
  evalMaxCost: document.getElementById('set-evalMaxCost').value || null,
248
249
  maxBuildFixAttempts: document.getElementById('set-maxBuildFixAttempts').value,
250
+ agentBusyReassignMs: document.getElementById('set-agentBusyReassignMs').value,
249
251
  ignoredCommentAuthors: document.getElementById('set-ignoredCommentAuthors').value,
250
252
  versionCheckInterval: document.getElementById('set-versionCheckInterval').value,
251
253
  ccModel: document.getElementById('set-ccModel').value,
@@ -1,3 +1,5 @@
1
+ <div class="engine-alert" id="ado-throttle-alert" style="display:none"></div>
2
+ <div class="engine-alert" id="gh-throttle-alert" style="display:none"></div>
1
3
  <section>
2
4
  <div id="engine-quick-stats" style="display:flex;gap:16px;margin-bottom:12px;font-size:11px;color:var(--muted)"></div>
3
5
  </section>
package/dashboard.js CHANGED
@@ -22,6 +22,7 @@ const shared = require('./engine/shared');
22
22
  const queries = require('./engine/queries');
23
23
  const teams = require('./engine/teams');
24
24
  const ado = require('./engine/ado');
25
+ const gh = require('./engine/github');
25
26
  const os = require('os');
26
27
 
27
28
  const { safeRead, safeReadDir, safeWrite, safeJson, safeJsonObj, safeJsonArr, safeUnlink, mutateJsonFileLocked, mutateWorkItems, getProjects: _getProjects, DONE_STATUSES, WI_STATUS, reopenWorkItem } = shared;
@@ -382,6 +383,8 @@ function getStatus() {
382
383
  verifyGuides: getVerifyGuides(),
383
384
  archivedPrds: getArchivedPrds(),
384
385
  engine: { ...getEngineState(), worktreeCount: _countWorktrees() },
386
+ adoThrottle: ado.getAdoThrottleState(),
387
+ ghThrottle: gh.getGhThrottleState(),
385
388
  dispatch: getDispatchQueue(),
386
389
  engineLog: getEngineLog(),
387
390
  metrics: getMetrics(),
@@ -3751,6 +3754,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3751
3754
  versionCheckInterval: [60000],
3752
3755
  maxBuildFixAttempts: [1, 10],
3753
3756
  adoPollStatusEvery: [1], adoPollCommentsEvery: [1],
3757
+ agentBusyReassignMs: [60000],
3754
3758
  };
3755
3759
  for (const [key, [min, max]] of Object.entries(numericFields)) {
3756
3760
  if (e[key] !== undefined) {
package/engine/ado.js CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  const path = require('path');
7
7
  const shared = require('./shared');
8
- const { exec, execAsync, getAdoOrgBase, addPrLink, log, ts, dateStamp, PR_STATUS } = shared;
8
+ const { exec, execAsync, getAdoOrgBase, addPrLink, log, ts, dateStamp, PR_STATUS, createThrottleTracker } = shared;
9
9
  const { getPrs } = require('./queries');
10
10
  const { mutateJsonFileLocked } = shared;
11
11
 
@@ -47,6 +47,11 @@ function votesToReviewStatus(votes) {
47
47
  let _adoTokenCache = { token: null, expiresAt: 0 };
48
48
  let _adoTokenFailedUntil = 0; // backoff: skip azureauth calls until this timestamp
49
49
 
50
+ // ─── ADO Throttle State ─────────────────────────────────────────────────────
51
+ // Tracks rate-limiting (HTTP 429/503) from ADO API responses.
52
+ // Uses shared createThrottleTracker factory: backoffMs starts at 60s, doubles, caps at 32 min.
53
+ const _adoThrottle = createThrottleTracker({ label: 'ado', baseBackoffMs: 60000, maxBackoffMs: 32 * 60000 });
54
+
50
55
  // ─── Auth Failure Tracking ──────────────────────────────────────────────────
51
56
  // Set when pollPrStatus encounters auth errors mid-loop. The engine checks this
52
57
  // to bypass the normal 6-tick cadence and re-poll on the next tick.
@@ -97,6 +102,14 @@ async function adoFetch(url, token, opts = {}) {
97
102
  signal: AbortSignal.timeout(30000),
98
103
  body,
99
104
  });
105
+ // ── Throttle detection: intercept 429/503 BEFORE generic !res.ok ──
106
+ if (res.status === 429 || res.status === 503) {
107
+ const retryAfterSec = parseInt(res.headers.get('Retry-After'), 10);
108
+ const retryAfterMs = (retryAfterSec > 0) ? retryAfterSec * 1000 : 0;
109
+ _adoThrottle.recordThrottle(retryAfterMs);
110
+ const state = _adoThrottle.getState();
111
+ throw new Error(`ADO API throttled (${res.status}): retry after ${Math.round((state.retryAfter - Date.now()) / 1000)}s`);
112
+ }
100
113
  if (!res.ok) throw new Error(`ADO API ${method} ${res.status}: ${res.statusText}`);
101
114
  const text = await res.text();
102
115
  if (!text || text.trimStart().startsWith('<')) {
@@ -110,7 +123,10 @@ async function adoFetch(url, token, opts = {}) {
110
123
  }
111
124
  throw new Error(`ADO returned HTML instead of JSON (likely auth redirect) for ${url.split('?')[0]}`);
112
125
  }
113
- return JSON.parse(text);
126
+ const json = JSON.parse(text);
127
+ // ── Success decay: decrement consecutiveHits, reset when fully recovered ──
128
+ _adoThrottle.recordSuccess();
129
+ return json;
114
130
  }
115
131
 
116
132
  /** Fetch raw text from ADO API (for build logs which aren't JSON). */
@@ -119,6 +135,14 @@ async function adoFetchText(url, token) {
119
135
  headers: { 'Authorization': `Bearer ${token}` },
120
136
  signal: AbortSignal.timeout(30000),
121
137
  });
138
+ // ── Throttle detection: intercept 429/503 BEFORE generic !res.ok ──
139
+ if (res.status === 429 || res.status === 503) {
140
+ const retryAfterSec = parseInt(res.headers.get('Retry-After'), 10);
141
+ const retryAfterMs = (retryAfterSec > 0) ? retryAfterSec * 1000 : 0;
142
+ _adoThrottle.recordThrottle(retryAfterMs);
143
+ const state = _adoThrottle.getState();
144
+ throw new Error(`ADO API throttled (${res.status}): retry after ${Math.round((state.retryAfter - Date.now()) / 1000)}s`);
145
+ }
122
146
  if (!res.ok) throw new Error(`ADO API ${res.status}: ${res.statusText}`);
123
147
  return res.text();
124
148
  }
@@ -858,6 +882,24 @@ async function fetchSinglePrBuildStatus(project, prNumber) {
858
882
  };
859
883
  }
860
884
 
885
+ // ─── ADO Throttle Queries ────────────────────────────────────────────────────
886
+
887
+ /** Returns true if ADO is throttled and retryAfter hasn't elapsed. Auto-clears when retryAfter passes. */
888
+ const isAdoThrottled = () => _adoThrottle.isThrottled();
889
+
890
+ /** Returns a snapshot of the current throttle state. Calls isAdoThrottled() for a fresh value. */
891
+ const getAdoThrottleState = () => _adoThrottle.getState();
892
+
893
+ /** Reset throttle state — exported for testing only. */
894
+ function _resetAdoThrottle() {
895
+ _adoThrottle._reset();
896
+ }
897
+
898
+ /** Set throttle state directly — exported for testing only. */
899
+ function _setAdoThrottleForTest(state) {
900
+ _adoThrottle._setForTest(state);
901
+ }
902
+
861
903
  module.exports = {
862
904
  getAdoToken,
863
905
  adoFetch,
@@ -867,7 +909,11 @@ module.exports = {
867
909
  checkLiveReviewStatus,
868
910
  needsAdoPollRetry,
869
911
  isAdoAuthError, // exported for testing
912
+ isAdoThrottled,
913
+ getAdoThrottleState,
870
914
  fetchAdoPrMetadata,
871
915
  fetchSinglePrBuildStatus,
916
+ _resetAdoThrottle, // exported for testing
917
+ _setAdoThrottleForTest, // exported for testing
872
918
  };
873
919
 
package/engine/github.js CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  const shared = require('./shared');
8
- const { exec, execAsync, getProjects, projectPrPath, projectWorkItemsPath, safeJson, safeWrite, mutateJsonFileLocked, MINIONS_DIR, addPrLink, getPrLinks, log, ts, dateStamp, PR_STATUS, PR_POLLABLE_STATUSES } = shared;
8
+ const { exec, execAsync, getProjects, projectPrPath, projectWorkItemsPath, safeJson, safeWrite, mutateJsonFileLocked, MINIONS_DIR, addPrLink, getPrLinks, log, ts, dateStamp, PR_STATUS, PR_POLLABLE_STATUSES, createThrottleTracker } = shared;
9
9
  const { getPrs } = require('./queries');
10
10
  const path = require('path');
11
11
 
@@ -68,13 +68,34 @@ function resetSlugBackoff(slug) {
68
68
  }
69
69
  }
70
70
 
71
+ // ─── GitHub Rate-Limit Throttle ────────────────────────────────────────────
72
+ // Tracks rate-limiting from GitHub API (gh CLI exits non-zero with rate-limit messages).
73
+ // GitHub rate limits reset hourly, so cap at 60 min.
74
+ const _ghThrottle = createThrottleTracker({ label: 'gh', baseBackoffMs: 60000, maxBackoffMs: 60 * 60000 });
75
+
76
+ /** Returns true if GitHub is rate-limited and retryAfter hasn't elapsed. */
77
+ const isGhThrottled = () => _ghThrottle.isThrottled();
78
+
79
+ /** Returns a snapshot of the current GitHub throttle state. */
80
+ const getGhThrottleState = () => _ghThrottle.getState();
81
+
71
82
  /** Run a `gh api` call and parse JSON result. Returns null on failure. */
72
83
  async function ghApi(endpoint, slug) {
73
84
  try {
74
85
  const cmd = `gh api "repos/${slug}${endpoint}"`;
75
86
  const result = await execAsync(cmd, { timeout: 30000, encoding: 'utf-8' });
76
- return JSON.parse(result);
87
+ const parsed = JSON.parse(result);
88
+ _ghThrottle.recordSuccess();
89
+ return parsed;
77
90
  } catch (e) {
91
+ const msg = e?.message || e?.stderr || '';
92
+ // Detect GitHub rate-limit errors from gh CLI output
93
+ if (/rate.?limit|secondary rate|429/i.test(msg)) {
94
+ // Try to extract retry seconds from "N seconds" pattern in error message
95
+ const secMatch = msg.match(/(\d+)\s*seconds?/i);
96
+ const retryAfterMs = secMatch ? parseInt(secMatch[1], 10) * 1000 : 0;
97
+ _ghThrottle.recordThrottle(retryAfterMs);
98
+ }
78
99
  log('warn', `GitHub API error (${endpoint}): ${e.message}`);
79
100
  return null;
80
101
  }
@@ -731,10 +752,13 @@ module.exports = {
731
752
  pollPrHumanComments,
732
753
  reconcilePrs,
733
754
  checkLiveReviewStatus,
755
+ isGhThrottled,
756
+ getGhThrottleState,
734
757
  // Exported for testing
735
758
  isSlugInBackoff,
736
759
  recordSlugFailure,
737
760
  resetSlugBackoff,
738
761
  _ghPollBackoff,
762
+ _ghThrottle, // exported for testing
739
763
  };
740
764
 
package/engine/shared.js CHANGED
@@ -1050,6 +1050,59 @@ function formatTranscriptEntry(t) {
1050
1050
  return '### ' + (t.agent || 'agent') + ' (' + (t.type || '') + ', Round ' + (t.round || '?') + ')\n\n' + (t.content || '');
1051
1051
  }
1052
1052
 
1053
+ // ── Throttle Tracker Factory ────────────────────────────────────────────────
1054
+ // Generic rate-limit tracker reusable by both ADO and GitHub integrations.
1055
+ // Returns an object with recordThrottle, recordSuccess, isThrottled, getState.
1056
+
1057
+ function createThrottleTracker({ label, baseBackoffMs = 60000, maxBackoffMs = 32 * 60000 } = {}) {
1058
+ let state = { throttled: false, retryAfter: 0, consecutiveHits: 0, backoffMs: baseBackoffMs };
1059
+
1060
+ function recordThrottle(retryAfterMs) {
1061
+ state.consecutiveHits++;
1062
+ state.backoffMs = Math.min(state.backoffMs * 2, maxBackoffMs);
1063
+ const waitMs = (retryAfterMs > 0) ? retryAfterMs : state.backoffMs;
1064
+ state.throttled = true;
1065
+ state.retryAfter = Date.now() + waitMs;
1066
+ log('warn', `[${label}] Throttled — retry after ${Math.round(waitMs / 1000)}s, consecutive hits: ${state.consecutiveHits}`);
1067
+ }
1068
+
1069
+ function recordSuccess() {
1070
+ if (state.consecutiveHits > 0) {
1071
+ state.consecutiveHits--;
1072
+ if (state.consecutiveHits === 0) {
1073
+ state.throttled = false;
1074
+ state.backoffMs = baseBackoffMs;
1075
+ }
1076
+ }
1077
+ }
1078
+
1079
+ function isThrottled() {
1080
+ if (!state.throttled) return false;
1081
+ if (Date.now() >= state.retryAfter) {
1082
+ // Auto-clear ALL state when retryAfter has elapsed
1083
+ state.throttled = false;
1084
+ state.backoffMs = baseBackoffMs;
1085
+ state.consecutiveHits = 0;
1086
+ return false;
1087
+ }
1088
+ return true;
1089
+ }
1090
+
1091
+ function getState() {
1092
+ return { throttled: isThrottled(), retryAfter: state.retryAfter, consecutiveHits: state.consecutiveHits };
1093
+ }
1094
+
1095
+ // Testing helpers
1096
+ function _reset() {
1097
+ state = { throttled: false, retryAfter: 0, consecutiveHits: 0, backoffMs: baseBackoffMs };
1098
+ }
1099
+ function _setForTest(overrides) {
1100
+ Object.assign(state, overrides);
1101
+ }
1102
+
1103
+ return { recordThrottle, recordSuccess, isThrottled, getState, _reset, _setForTest };
1104
+ }
1105
+
1053
1106
  module.exports = {
1054
1107
  MINIONS_DIR,
1055
1108
  PR_LINKS_PATH,
@@ -1113,5 +1166,6 @@ module.exports = {
1113
1166
  slugify,
1114
1167
  formatTranscriptEntry,
1115
1168
  _logBuffer, // exported for testing
1169
+ createThrottleTracker,
1116
1170
  };
1117
1171
 
package/engine.js CHANGED
@@ -1445,8 +1445,8 @@ function reconcileItemsWithPrs(items, allPrs, { onlyIds } = {}) {
1445
1445
  // ─── Inbox Consolidation (extracted to engine/consolidation.js) ──────────────
1446
1446
 
1447
1447
  const { consolidateInbox } = require('./engine/consolidation');
1448
- const { pollPrStatus, pollPrHumanComments, reconcilePrs, checkLiveReviewStatus: adoCheckLiveReview, needsAdoPollRetry, getAdoToken } = require('./engine/ado');
1449
- const { pollPrStatus: ghPollPrStatus, pollPrHumanComments: ghPollPrHumanComments, reconcilePrs: ghReconcilePrs, checkLiveReviewStatus: ghCheckLiveReview } = require('./engine/github');
1448
+ const { pollPrStatus, pollPrHumanComments, reconcilePrs, checkLiveReviewStatus: adoCheckLiveReview, needsAdoPollRetry, getAdoToken, isAdoThrottled } = require('./engine/ado');
1449
+ const { pollPrStatus: ghPollPrStatus, pollPrHumanComments: ghPollPrHumanComments, reconcilePrs: ghReconcilePrs, checkLiveReviewStatus: ghCheckLiveReview, isGhThrottled } = require('./engine/github');
1450
1450
 
1451
1451
  // ─── State Snapshot ─────────────────────────────────────────────────────────
1452
1452
 
@@ -3130,11 +3130,15 @@ async function tickInner() {
3130
3130
  // Awaited so PR state is consistent before discoverWork reads it
3131
3131
  // Also re-polls early if previous tick had ADO auth failures (stale build status recovery)
3132
3132
  if (tickCount % adoPollStatusEvery === 0 || needsAdoPollRetry()) {
3133
- if (adoPollEnabled) {
3133
+ if (adoPollEnabled && !isAdoThrottled()) {
3134
3134
  try { await pollPrStatus(config); } catch (err) { log('warn', `ADO PR status poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
3135
+ } else if (adoPollEnabled && isAdoThrottled()) {
3136
+ log('info', '[ado] PR status poll skipped — throttled');
3135
3137
  }
3136
- if (ghPollEnabled) {
3138
+ if (ghPollEnabled && !isGhThrottled()) {
3137
3139
  try { await ghPollPrStatus(config); } catch (err) { log('warn', `GitHub PR status poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
3140
+ } else if (ghPollEnabled && isGhThrottled()) {
3141
+ log('info', '[gh] PR status poll skipped — throttled');
3138
3142
  }
3139
3143
  try { await processPendingRebases(config); } catch (err) { log('warn', `Pending rebase processing error: ${err?.message || err}`); }
3140
3144
  // Sync PR status back to PRD items (missing → done when active PR exists)
@@ -3157,11 +3161,15 @@ async function tickInner() {
3157
3161
 
3158
3162
  // 2.7. Poll PR threads for human comments (every adoPollCommentsEvery ticks, default ~12 minutes)
3159
3163
  if (tickCount % adoPollCommentsEvery === 0) {
3160
- if (adoPollEnabled) {
3164
+ if (adoPollEnabled && !isAdoThrottled()) {
3161
3165
  try { await pollPrHumanComments(config); } catch (err) { log('warn', `ADO PR comment poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
3166
+ } else if (adoPollEnabled && isAdoThrottled()) {
3167
+ log('info', '[ado] PR comment poll skipped — throttled');
3162
3168
  }
3163
- if (ghPollEnabled) {
3169
+ if (ghPollEnabled && !isGhThrottled()) {
3164
3170
  try { await ghPollPrHumanComments(config); } catch (err) { log('warn', `GitHub PR comment poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
3171
+ } else if (ghPollEnabled && isGhThrottled()) {
3172
+ log('info', '[gh] PR comment poll skipped — throttled');
3165
3173
  }
3166
3174
  // Reconciliation runs regardless of poll flags — it's a recovery sweep, not a convenience poll
3167
3175
  try { await reconcilePrs(config); } catch (err) { log('warn', `ADO PR reconciliation error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.954",
3
+ "version": "0.1.956",
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"