@yemi33/minions 0.1.1688 → 0.1.1690

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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1690 (2026-05-03)
4
+
5
+ ### Features
6
+ - prefer az cli for ado tokens (#1996)
7
+
8
+ ### Other
9
+ - test(consolidation): add unit tests for buildConsolidationPrompt, consolidateWithRegex, consolidateWithLLM, archiveInboxFiles (#1998)
10
+ - test(preflight): add unit tests for runtime-fleet helpers (_distinctRuntimes, _checkRuntimeBinary, _warmModelCache, _fleetSummaryResults, _modelDiscoveryResults) (#1997)
11
+
3
12
  ## 0.1.1688 (2026-05-02)
4
13
 
5
14
  ### Fixes
@@ -141,7 +141,7 @@ The engine directly polls the host REST API for **all** PR metadata: build/CI st
141
141
  | `buildStatus` | PR statuses (codecoverage/deploy/build/ci contexts) | `passing` / `failing` / `running` / `none` |
142
142
  | `buildFailReason` | Failed status description | Set on failure, cleared otherwise |
143
143
 
144
- **Auth:** Bearer token via `azureauth ado token --mode iwa --mode broker --output token --timeout 1` (cached 30 minutes). The `--timeout 1` flag is required — without it, azureauth can hang indefinitely in headless sessions. (GitHub polling uses the ambient `gh` CLI credentials, not azureauth.)
144
+ **Auth:** Bearer token via shared `engine/ado-token.js`: prefer `az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv`, then fall back to `azureauth ado token --mode iwa --mode broker --output token --timeout 1` (cached 30 minutes). The `--timeout 1` flag is required — without it, azureauth can hang indefinitely in headless sessions. (GitHub polling uses the ambient `gh` CLI credentials, not azureauth.)
145
145
 
146
146
  This feeds `discoverFromPrs` — when `buildStatus` flips to `"failing"`, the next discovery tick dispatches a fix agent. When `status` becomes `"merged"`, the PR drops out of active polling.
147
147
 
@@ -1,24 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Wrapper for @azure-devops/mcp that fetches an ADO token via azureauth
4
- * broker (no browser popup) and sets AZURE_DEVOPS_EXT_PAT before launching
5
- * the MCP server.
3
+ * Wrapper for @azure-devops/mcp that fetches an ADO token via the shared
4
+ * az-first provider chain and sets AZURE_DEVOPS_EXT_PAT before launching the
5
+ * MCP server.
6
6
  */
7
- const { execSync, spawn } = require('child_process');
8
- const path = require('path');
7
+ const { spawn } = require('child_process');
8
+ const { acquireAdoTokenSync } = require('./ado-token');
9
9
 
10
- // Fetch token via azureauth broker (corp tool, no browser)
11
10
  let token;
12
11
  try {
13
- token = execSync('azureauth ado token --mode broker --output token --timeout 1', {
14
- encoding: 'utf8',
15
- timeout: 30000,
16
- windowsHide: true,
17
- }).trim();
12
+ token = acquireAdoTokenSync().token;
18
13
  } catch (e) {
19
- // Broker failed do NOT fall back to web mode (opens browser in automated context)
20
- process.stderr.write('ado-mcp-wrapper: Broker auth failed: ' + e.message + '\n');
21
- process.stderr.write('ado-mcp-wrapper: Run "azureauth ado token --mode web" manually to refresh\n');
14
+ process.stderr.write('ado-mcp-wrapper: ADO auth failed: ' + e.message + '\n');
15
+ process.stderr.write('ado-mcp-wrapper: Run "az login" or refresh azureauth manually, then retry\n');
22
16
  process.exit(1);
23
17
  }
24
18
 
@@ -31,7 +25,7 @@ const child = spawn(process.platform === 'win32' ? 'npx.cmd' : 'npx', [
31
25
  ...args
32
26
  ], {
33
27
  stdio: 'inherit',
34
- env: { ...process.env, AZURE_DEVOPS_EXT_PAT: token },
28
+ env: { ...process.env, AZURE_DEVOPS_EXT_PAT: token, AZURE_DEVOPS_EXT_AZURE_RM_PAT: token },
35
29
  windowsHide: true,
36
30
  shell: false,
37
31
  });
@@ -3,8 +3,8 @@
3
3
  * engine/ado-status.js — CLI shim for querying PR and build status.
4
4
  *
5
5
  * Agents steered to "check on the builds" or "is CI green for PR #123" should
6
- * use this instead of raw curl + azureauth calls. All ADO auth and retry logic
7
- * is handled by ado.js internally.
6
+ * use this instead of raw curl + ad-hoc auth calls. All ADO auth and retry
7
+ * logic is handled by ado.js internally.
8
8
  *
9
9
  * Usage:
10
10
  * node engine/ado-status.js <prNumber>
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Shared Azure DevOps token acquisition.
3
+ *
4
+ * Prefer Azure CLI because it is the most common authenticated tool in agent
5
+ * environments; keep azureauth as the non-interactive fallback for corp setups.
6
+ */
7
+
8
+ const { exec, execSync } = require('child_process');
9
+
10
+ const ADO_RESOURCE_ID = '499b84ac-1321-427f-aa17-267ca6975798';
11
+ const AZ_CLI_ADO_TOKEN_COMMAND = `az account get-access-token --resource ${ADO_RESOURCE_ID} --query accessToken -o tsv`;
12
+ const AZUREAUTH_ADO_TOKEN_COMMAND = 'azureauth ado token --mode iwa --mode broker --output token --timeout 1';
13
+ const DEFAULT_ADO_TOKEN_TIMEOUT_MS = 30000;
14
+
15
+ const ADO_TOKEN_PROVIDERS = Object.freeze([
16
+ Object.freeze({ source: 'az', command: AZ_CLI_ADO_TOKEN_COMMAND }),
17
+ Object.freeze({ source: 'azureauth', command: AZUREAUTH_ADO_TOKEN_COMMAND }),
18
+ ]);
19
+
20
+ function normalizeAdoToken(value) {
21
+ return String(value || '').trim();
22
+ }
23
+
24
+ function isLikelyAdoToken(token) {
25
+ return typeof token === 'string' && token.startsWith('eyJ');
26
+ }
27
+
28
+ function _commandOptions({ timeout = DEFAULT_ADO_TOKEN_TIMEOUT_MS, encoding = 'utf8', windowsHide = true } = {}) {
29
+ return { encoding, timeout, windowsHide };
30
+ }
31
+
32
+ function _attemptMessage(attempt) {
33
+ return `${attempt.source}: ${attempt.error}`;
34
+ }
35
+
36
+ function _buildAdoTokenError(attempts) {
37
+ const err = new Error(`Failed to get ADO token via az CLI or azureauth: ${attempts.map(_attemptMessage).join('; ')}`);
38
+ err.attempts = attempts;
39
+ return err;
40
+ }
41
+
42
+ function _recordInvalidToken(attempts, provider) {
43
+ attempts.push({ source: provider.source, command: provider.command, error: 'invalid token output' });
44
+ }
45
+
46
+ function acquireAdoTokenSync({ execSync: run = execSync, timeout, encoding, windowsHide } = {}) {
47
+ const opts = _commandOptions({ timeout, encoding, windowsHide });
48
+ const attempts = [];
49
+ for (const provider of ADO_TOKEN_PROVIDERS) {
50
+ try {
51
+ const token = normalizeAdoToken(run(provider.command, opts));
52
+ if (isLikelyAdoToken(token)) {
53
+ return { token, source: provider.source, command: provider.command };
54
+ }
55
+ _recordInvalidToken(attempts, provider);
56
+ } catch (e) {
57
+ attempts.push({ source: provider.source, command: provider.command, error: e.message });
58
+ }
59
+ }
60
+ throw _buildAdoTokenError(attempts);
61
+ }
62
+
63
+ function _defaultExecAsync(command, opts) {
64
+ return new Promise((resolve, reject) => {
65
+ exec(command, opts, (err, stdout, stderr) => {
66
+ if (err) {
67
+ err.stdout = stdout;
68
+ err.stderr = stderr;
69
+ reject(err);
70
+ return;
71
+ }
72
+ resolve(stdout);
73
+ });
74
+ });
75
+ }
76
+
77
+ async function acquireAdoToken({ execAsync: run = _defaultExecAsync, timeout, encoding, windowsHide } = {}) {
78
+ const opts = _commandOptions({ timeout, encoding, windowsHide });
79
+ const attempts = [];
80
+ for (const provider of ADO_TOKEN_PROVIDERS) {
81
+ try {
82
+ const token = normalizeAdoToken(await run(provider.command, opts));
83
+ if (isLikelyAdoToken(token)) {
84
+ return { token, source: provider.source, command: provider.command };
85
+ }
86
+ _recordInvalidToken(attempts, provider);
87
+ } catch (e) {
88
+ attempts.push({ source: provider.source, command: provider.command, error: e.message });
89
+ }
90
+ }
91
+ throw _buildAdoTokenError(attempts);
92
+ }
93
+
94
+ module.exports = {
95
+ ADO_RESOURCE_ID,
96
+ AZ_CLI_ADO_TOKEN_COMMAND,
97
+ AZUREAUTH_ADO_TOKEN_COMMAND,
98
+ DEFAULT_ADO_TOKEN_TIMEOUT_MS,
99
+ ADO_TOKEN_PROVIDERS,
100
+ acquireAdoToken,
101
+ acquireAdoTokenSync,
102
+ isLikelyAdoToken,
103
+ normalizeAdoToken,
104
+ };
package/engine/ado.js CHANGED
@@ -8,6 +8,7 @@ const shared = require('./shared');
8
8
  const { exec, execAsync, getAdoOrgBase, log, ts, dateStamp, PR_STATUS, createThrottleTracker } = shared;
9
9
  const { getPrs } = require('./queries');
10
10
  const { mutateJsonFileLocked } = shared;
11
+ const { acquireAdoToken } = require('./ado-token');
11
12
 
12
13
  // Lazy require to avoid circular dependency — only needed for engine().handlePostMerge
13
14
  let _engine = null;
@@ -199,7 +200,7 @@ function votesToReviewStatus(votes) {
199
200
  // ─── ADO Token Cache ─────────────────────────────────────────────────────────
200
201
 
201
202
  let _adoTokenCache = { token: null, expiresAt: 0 };
202
- let _adoTokenFailedUntil = 0; // backoff: skip azureauth calls until this timestamp
203
+ let _adoTokenFailedUntil = 0; // backoff: skip token acquisition calls until this timestamp
203
204
 
204
205
  // ─── ADO Throttle State ─────────────────────────────────────────────────────
205
206
  // Tracks rate-limiting (HTTP 429/503) from ADO API responses.
@@ -224,23 +225,17 @@ async function getAdoToken() {
224
225
  if (_adoTokenCache.token && Date.now() < _adoTokenCache.expiresAt) {
225
226
  return _adoTokenCache.token;
226
227
  }
227
- // If recent fetch failed, don't retry until backoff expires (avoids repeated browser popups)
228
+ // If recent fetch failed, don't retry until backoff expires.
228
229
  if (Date.now() < _adoTokenFailedUntil) return null;
229
230
  try {
230
- // azureauth supports multiple --mode flags as an ordered fallback chain:
231
- // tries IWA (Integrated Windows Auth) first, falls back to broker if unavailable.
232
- // Uses execAsync to avoid blocking the event loop on Windows (spawnSync ETIMEDOUT).
233
- const token = (await execAsync('azureauth ado token --mode iwa --mode broker --output token --timeout 1', {
234
- timeout: 15000, encoding: 'utf-8', windowsHide: true })).trim();
235
- if (token && token.startsWith('eyJ')) {
236
- _adoTokenCache = { token, expiresAt: Date.now() + 30 * 60 * 1000 };
237
- _adoTokenFailedUntil = 0;
238
- return token;
239
- }
231
+ const { token } = await acquireAdoToken({ execAsync, timeout: 15000 });
232
+ _adoTokenCache = { token, expiresAt: Date.now() + 30 * 60 * 1000 };
233
+ _adoTokenFailedUntil = 0;
234
+ return token;
240
235
  } catch (e) {
241
236
  log('warn', `Failed to get ADO token: ${e.message}`);
242
237
  }
243
- // Back off for 10 minutes to avoid spamming browser auth popups
238
+ // Back off for 10 minutes to avoid spamming auth commands.
244
239
  _adoTokenFailedUntil = Date.now() + 10 * 60 * 1000;
245
240
  return null;
246
241
  }
@@ -283,7 +283,7 @@ function consolidateWithRegex(items, files) {
283
283
  else if (nameLower.includes('explore')) category = 'exploration';
284
284
  else if (contentLower.includes('bug') || contentLower.includes('fix')) category = 'bugs-fixes';
285
285
 
286
- const numberedPattern = /^\d+\.\s+\*\*(.+?)\*\*\s*[\u2014\u2013:-]\s*(.+)/;
286
+ const numberedPattern = /^\d+\.\s+\*\*(.+?)\*\*\s*(?:--|[\u2014\u2013:-])\s*(.+)/;
287
287
  const bulletPattern = /^[-*]\s+\*\*(.+?)\*\*[:\s]+(.+)/;
288
288
  const sectionPattern = /^###+\s+(.+)/;
289
289
  const importantKeywords = /\b(must|never|always|convention|pattern|gotcha|warning|important|rule|tip|note that)\b/i;
@@ -470,4 +470,9 @@ module.exports = {
470
470
  consolidateInbox,
471
471
  classifyToKnowledgeBase,
472
472
  checkDuplicateHash,
473
+ // exported for testing
474
+ buildConsolidationPrompt,
475
+ consolidateWithLLM,
476
+ consolidateWithRegex,
477
+ archiveInboxFiles,
473
478
  };
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-02T19:44:35.476Z"
4
+ "cachedAt": "2026-05-03T15:02:36.167Z"
5
5
  }
@@ -35,9 +35,9 @@
35
35
  const fs = require('fs');
36
36
  const os = require('os');
37
37
  const path = require('path');
38
- const { execSync } = require('child_process');
39
38
  const { runFile, cleanChildEnv, killGracefully, killImmediate, ts } = require('./shared');
40
39
  const { resolveRuntime } = require('./runtimes');
40
+ const { acquireAdoTokenSync, isLikelyAdoToken } = require('./ado-token');
41
41
 
42
42
  // ─── Pure helpers (exported for tests) ──────────────────────────────────────
43
43
 
@@ -129,20 +129,19 @@ function normalizeRuntimeExit(code, signal) {
129
129
  return 1;
130
130
  }
131
131
 
132
- function injectAdoTokenEnv(env, { execSync: _execSync = execSync, warn = (msg) => process.stderr.write(msg + '\n') } = {}) {
133
- let token;
132
+ function injectAdoTokenEnv(env, { execSync: _execSync, acquireToken, warn = (msg) => process.stderr.write(msg + '\n') } = {}) {
133
+ let result;
134
134
  try {
135
- token = String(_execSync('azureauth ado token --mode iwa --mode broker --output token --timeout 1', {
136
- encoding: 'utf8',
137
- timeout: 30000,
138
- windowsHide: true,
139
- }) || '').trim();
135
+ result = typeof acquireToken === 'function'
136
+ ? acquireToken()
137
+ : acquireAdoTokenSync({ execSync: _execSync });
140
138
  } catch (err) {
141
139
  warn(`spawn-agent.js: ADO token fetch failed: ${err.message}`);
142
140
  return false;
143
141
  }
144
- if (!token || !token.startsWith('eyJ')) {
145
- warn('spawn-agent.js: invalid ADO token from azureauth; continuing without Azure DevOps PAT env');
142
+ const token = typeof result === 'string' ? result : result?.token;
143
+ if (!isLikelyAdoToken(token)) {
144
+ warn('spawn-agent.js: invalid ADO token; continuing without Azure DevOps PAT env');
146
145
  return false;
147
146
  }
148
147
  env.AZURE_DEVOPS_EXT_PAT = token;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1688",
3
+ "version": "0.1.1690",
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"