@yemi33/minions 0.1.1688 → 0.1.1689
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 +5 -0
- package/docs/auto-discovery.md +1 -1
- package/engine/ado-mcp-wrapper.js +9 -15
- package/engine/ado-status.js +2 -2
- package/engine/ado-token.js +104 -0
- package/engine/ado.js +8 -13
- package/engine/copilot-models.json +1 -1
- package/engine/spawn-agent.js +9 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/docs/auto-discovery.md
CHANGED
|
@@ -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
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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 {
|
|
8
|
-
const
|
|
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 =
|
|
14
|
-
encoding: 'utf8',
|
|
15
|
-
timeout: 30000,
|
|
16
|
-
windowsHide: true,
|
|
17
|
-
}).trim();
|
|
12
|
+
token = acquireAdoTokenSync().token;
|
|
18
13
|
} catch (e) {
|
|
19
|
-
|
|
20
|
-
process.stderr.write('ado-mcp-wrapper:
|
|
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
|
});
|
package/engine/ado-status.js
CHANGED
|
@@ -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 +
|
|
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
|
|
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
|
|
228
|
+
// If recent fetch failed, don't retry until backoff expires.
|
|
228
229
|
if (Date.now() < _adoTokenFailedUntil) return null;
|
|
229
230
|
try {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
|
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
|
}
|
package/engine/spawn-agent.js
CHANGED
|
@@ -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
|
|
133
|
-
let
|
|
132
|
+
function injectAdoTokenEnv(env, { execSync: _execSync, acquireToken, warn = (msg) => process.stderr.write(msg + '\n') } = {}) {
|
|
133
|
+
let result;
|
|
134
134
|
try {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.1689",
|
|
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"
|