@yemi33/minions 0.1.934 → 0.1.936
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 +3 -1
- package/dashboard/js/settings.js +44 -3
- package/dashboard.js +19 -1
- package/docs/teams-setup.md +53 -1
- package/engine/preflight.js +10 -4
- package/engine/shared.js +7 -1
- package/engine/teams.js +47 -10
- package/engine.js +9 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.936 (2026-04-14)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- make ADO poll frequency configurable and ungate reconcilePrs
|
|
7
|
+
- certificate-based auth for Teams integration (#1027)
|
|
6
8
|
- add adoPollEnabled/ghPollEnabled engine settings
|
|
7
9
|
- doc-chat abort kills LLM process + queued messages auto-process
|
|
8
10
|
- add /api/work-items/reopen endpoint and reopen-work-item CC action (#982)
|
package/dashboard/js/settings.js
CHANGED
|
@@ -14,6 +14,7 @@ async function openSettings() {
|
|
|
14
14
|
const e = data.engine || {};
|
|
15
15
|
const c = data.claude || {};
|
|
16
16
|
const agents = data.agents || {};
|
|
17
|
+
const t = data.teams || {};
|
|
17
18
|
|
|
18
19
|
const agentRows = Object.entries(agents).map(function([id, a]) {
|
|
19
20
|
return '<tr>' +
|
|
@@ -48,8 +49,12 @@ async function openSettings() {
|
|
|
48
49
|
settingsToggle('Auto-decompose', 'set-autoDecompose', e.autoDecompose !== false, 'Large implement items are auto-split into sub-tasks') +
|
|
49
50
|
settingsToggle('Allow Temp Agents', 'set-allowTempAgents', !!e.allowTempAgents, 'Spawn ephemeral agents when all permanent agents are busy') +
|
|
50
51
|
settingsToggle('Auto-archive Plans', 'set-autoArchive', !!e.autoArchive, 'Automatically archive plans after verify completes (off = manual archive via dashboard)') +
|
|
51
|
-
settingsToggle('ADO Polling', 'set-adoPollEnabled', e.adoPollEnabled !== false, 'Poll ADO PR status
|
|
52
|
-
settingsToggle('GitHub Polling', 'set-ghPollEnabled', e.ghPollEnabled !== false, 'Poll GitHub PR status
|
|
52
|
+
settingsToggle('ADO Polling', 'set-adoPollEnabled', e.adoPollEnabled !== false, 'Poll ADO PR status and comments each tick (reconciliation always runs)') +
|
|
53
|
+
settingsToggle('GitHub Polling', 'set-ghPollEnabled', e.ghPollEnabled !== false, 'Poll GitHub PR status and comments each tick (reconciliation always runs)') +
|
|
54
|
+
'</div>' +
|
|
55
|
+
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px">' +
|
|
56
|
+
settingsField('ADO Status Poll Frequency', 'set-adoPollStatusEvery', e.adoPollStatusEvery || 6, 'ticks', 'Poll ADO PR build/review/merge status every N ticks (~6 min at default tick rate)') +
|
|
57
|
+
settingsField('ADO Comments Poll Frequency', 'set-adoPollCommentsEvery', e.adoPollCommentsEvery || 12, 'ticks', 'Poll ADO PR human comments every N ticks (~12 min at default tick rate)') +
|
|
53
58
|
'</div>' +
|
|
54
59
|
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px">' +
|
|
55
60
|
settingsField('Eval Max Iterations', 'set-evalMaxIterations', e.evalMaxIterations || 3, '', 'Max review→fix cycles before escalating (1-10)') +
|
|
@@ -96,6 +101,28 @@ async function openSettings() {
|
|
|
96
101
|
'</div>' +
|
|
97
102
|
'</div>' +
|
|
98
103
|
|
|
104
|
+
'<h3 style="font-size:13px;color:var(--blue);margin-bottom:8px">Teams Integration</h3>' +
|
|
105
|
+
'<div style="display:flex;flex-direction:column;gap:6px;margin-bottom:8px">' +
|
|
106
|
+
settingsToggle('Enable Teams', 'set-teams-enabled', !!t.enabled, 'Connect Minions to Microsoft Teams') +
|
|
107
|
+
'</div>' +
|
|
108
|
+
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px">' +
|
|
109
|
+
settingsField('App ID', 'set-teams-appId', t.appId || '', '', 'Microsoft App ID from Azure Bot Configuration') +
|
|
110
|
+
settingsField('App Password', 'set-teams-appPassword', t.appPassword || '', '', 'Client secret (leave blank for certificate auth)') +
|
|
111
|
+
'</div>' +
|
|
112
|
+
'<div style="font-size:10px;color:var(--muted);margin-bottom:4px;font-weight:600">Certificate Auth (alternative to client secret)</div>' +
|
|
113
|
+
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px">' +
|
|
114
|
+
settingsField('Certificate Path', 'set-teams-certPath', t.certPath || '', '', 'Path to PEM certificate file') +
|
|
115
|
+
settingsField('Private Key Path', 'set-teams-privateKeyPath', t.privateKeyPath || '', '', 'Path to PEM private key file') +
|
|
116
|
+
settingsField('Tenant ID', 'set-teams-tenantId', t.tenantId || '', '', 'Azure AD tenant ID (required for cert auth)') +
|
|
117
|
+
'</div>' +
|
|
118
|
+
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px">' +
|
|
119
|
+
settingsField('Notify Events', 'set-teams-notifyEvents', (t.notifyEvents || []).join(', '), '', 'Comma-separated event types to notify') +
|
|
120
|
+
settingsField('Inbox Poll Interval', 'set-teams-inboxPollInterval', t.inboxPollInterval || 15000, 'ms', 'How often to check for Teams messages') +
|
|
121
|
+
'</div>' +
|
|
122
|
+
'<div style="display:flex;flex-direction:column;gap:6px;margin-bottom:16px">' +
|
|
123
|
+
settingsToggle('CC Mirror', 'set-teams-ccMirror', t.ccMirror !== false, 'Mirror Command Center responses to Teams') +
|
|
124
|
+
'</div>' +
|
|
125
|
+
|
|
99
126
|
'<h3 style="font-size:13px;color:var(--blue);margin-bottom:8px">Claude CLI</h3>' +
|
|
100
127
|
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px">' +
|
|
101
128
|
settingsField('Output Format', 'set-outputFormat', c.outputFormat || 'stream-json', '', '') +
|
|
@@ -212,6 +239,8 @@ async function saveSettings() {
|
|
|
212
239
|
autoArchive: document.getElementById('set-autoArchive').checked,
|
|
213
240
|
adoPollEnabled: document.getElementById('set-adoPollEnabled').checked,
|
|
214
241
|
ghPollEnabled: document.getElementById('set-ghPollEnabled').checked,
|
|
242
|
+
adoPollStatusEvery: document.getElementById('set-adoPollStatusEvery').value,
|
|
243
|
+
adoPollCommentsEvery: document.getElementById('set-adoPollCommentsEvery').value,
|
|
215
244
|
evalMaxIterations: document.getElementById('set-evalMaxIterations').value,
|
|
216
245
|
evalMaxCost: document.getElementById('set-evalMaxCost').value || null,
|
|
217
246
|
maxBuildFixAttempts: document.getElementById('set-maxBuildFixAttempts').value,
|
|
@@ -236,6 +265,18 @@ async function saveSettings() {
|
|
|
236
265
|
permissionMode: document.getElementById('set-permissionMode').value,
|
|
237
266
|
};
|
|
238
267
|
|
|
268
|
+
const teamsPayload = {
|
|
269
|
+
enabled: document.getElementById('set-teams-enabled').checked,
|
|
270
|
+
appId: document.getElementById('set-teams-appId').value,
|
|
271
|
+
appPassword: document.getElementById('set-teams-appPassword').value,
|
|
272
|
+
certPath: document.getElementById('set-teams-certPath').value,
|
|
273
|
+
privateKeyPath: document.getElementById('set-teams-privateKeyPath').value,
|
|
274
|
+
tenantId: document.getElementById('set-teams-tenantId').value,
|
|
275
|
+
notifyEvents: document.getElementById('set-teams-notifyEvents').value,
|
|
276
|
+
inboxPollInterval: document.getElementById('set-teams-inboxPollInterval').value,
|
|
277
|
+
ccMirror: document.getElementById('set-teams-ccMirror').checked,
|
|
278
|
+
};
|
|
279
|
+
|
|
239
280
|
const agentsPayload = {};
|
|
240
281
|
document.querySelectorAll('[data-agent][data-field]').forEach(function(el) {
|
|
241
282
|
const id = el.dataset.agent;
|
|
@@ -247,7 +288,7 @@ async function saveSettings() {
|
|
|
247
288
|
// Save config
|
|
248
289
|
const res = await fetch('/api/settings', {
|
|
249
290
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
250
|
-
body: JSON.stringify({ engine: enginePayload, claude: claudePayload, agents: agentsPayload })
|
|
291
|
+
body: JSON.stringify({ engine: enginePayload, claude: claudePayload, agents: agentsPayload, teams: teamsPayload })
|
|
251
292
|
});
|
|
252
293
|
const result = await res.json();
|
|
253
294
|
if (!res.ok) throw new Error(result.error);
|
package/dashboard.js
CHANGED
|
@@ -3702,6 +3702,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3702
3702
|
engine: { ...shared.ENGINE_DEFAULTS, ...(config.engine || {}) },
|
|
3703
3703
|
claude: { ...shared.DEFAULT_CLAUDE, ...(config.claude || {}) },
|
|
3704
3704
|
agents: config.agents || {},
|
|
3705
|
+
teams: { ...shared.ENGINE_DEFAULTS.teams, ...(config.teams || {}) },
|
|
3705
3706
|
routing,
|
|
3706
3707
|
});
|
|
3707
3708
|
} catch (e) { return jsonReply(res, 500, { error: e.message }); }
|
|
@@ -3729,6 +3730,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3729
3730
|
meetingRoundTimeout: [60000],
|
|
3730
3731
|
versionCheckInterval: [60000],
|
|
3731
3732
|
maxBuildFixAttempts: [1, 10],
|
|
3733
|
+
adoPollStatusEvery: [1], adoPollCommentsEvery: [1],
|
|
3732
3734
|
};
|
|
3733
3735
|
for (const [key, [min, max]] of Object.entries(numericFields)) {
|
|
3734
3736
|
if (e[key] !== undefined) {
|
|
@@ -3795,6 +3797,22 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3795
3797
|
}
|
|
3796
3798
|
}
|
|
3797
3799
|
|
|
3800
|
+
if (body.teams) {
|
|
3801
|
+
if (!config.teams) config.teams = {};
|
|
3802
|
+
const tm = body.teams;
|
|
3803
|
+
if (tm.enabled !== undefined) config.teams.enabled = !!tm.enabled;
|
|
3804
|
+
for (const key of ['appId', 'appPassword', 'certPath', 'privateKeyPath', 'tenantId']) {
|
|
3805
|
+
if (tm[key] !== undefined) config.teams[key] = String(tm[key] || '');
|
|
3806
|
+
}
|
|
3807
|
+
if (tm.notifyEvents !== undefined) {
|
|
3808
|
+
config.teams.notifyEvents = Array.isArray(tm.notifyEvents) ? tm.notifyEvents : String(tm.notifyEvents || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
3809
|
+
}
|
|
3810
|
+
if (tm.inboxPollInterval !== undefined) config.teams.inboxPollInterval = Math.max(5000, Number(tm.inboxPollInterval) || 15000);
|
|
3811
|
+
if (tm.ccMirror !== undefined) config.teams.ccMirror = !!tm.ccMirror;
|
|
3812
|
+
// Invalidate cached adapter so credential changes take effect
|
|
3813
|
+
teams._resetAdapter();
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3798
3816
|
safeWrite(configPath, config);
|
|
3799
3817
|
// Refresh in-memory CONFIG so subsequent reads see the update
|
|
3800
3818
|
reloadConfig();
|
|
@@ -4571,7 +4589,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4571
4589
|
|
|
4572
4590
|
// Settings
|
|
4573
4591
|
{ method: 'GET', path: '/api/settings', desc: 'Return current engine + claude + routing config', handler: handleSettingsRead },
|
|
4574
|
-
{ method: 'POST', path: '/api/settings', desc: 'Update engine + claude + agent config', params: 'engine?, claude?, agents?', handler: handleSettingsUpdate },
|
|
4592
|
+
{ method: 'POST', path: '/api/settings', desc: 'Update engine + claude + agent + teams config', params: 'engine?, claude?, agents?, teams?', handler: handleSettingsUpdate },
|
|
4575
4593
|
{ method: 'POST', path: '/api/settings/routing', desc: 'Update routing.md', params: 'content', handler: handleSettingsRouting },
|
|
4576
4594
|
{ method: 'POST', path: '/api/settings/reset', desc: 'Reset engine + claude + agent settings to defaults', handler: handleSettingsReset },
|
|
4577
4595
|
|
package/docs/teams-setup.md
CHANGED
|
@@ -84,7 +84,10 @@ Add a `teams` section to your `config.json`:
|
|
|
84
84
|
|-------|-------------|
|
|
85
85
|
| `enabled` | Master switch — `true` to activate Teams integration |
|
|
86
86
|
| `appId` | Microsoft App ID from the Azure Bot Configuration page |
|
|
87
|
-
| `appPassword` | Client secret value from Entra ID Certificates & secrets |
|
|
87
|
+
| `appPassword` | Client secret value from Entra ID Certificates & secrets (leave blank for certificate auth) |
|
|
88
|
+
| `certPath` | Path to PEM certificate file (certificate auth only) |
|
|
89
|
+
| `privateKeyPath` | Path to PEM private key file (certificate auth only) |
|
|
90
|
+
| `tenantId` | Azure AD tenant ID (required for certificate auth) |
|
|
88
91
|
| `notifyEvents` | Which events trigger Teams notifications (see below) |
|
|
89
92
|
| `ccMirror` | Mirror CC dashboard responses to Teams (`true`/`false`) |
|
|
90
93
|
| `inboxPollInterval` | How often to check for new Teams messages, in ms (default: 15000) |
|
|
@@ -93,6 +96,55 @@ Add a `teams` section to your `config.json`:
|
|
|
93
96
|
|
|
94
97
|
> **Security note:** `config.json` is gitignored by default and should never be committed. For shared machines, consider setting the app password via an environment variable and reading it in your config setup.
|
|
95
98
|
|
|
99
|
+
### Certificate Auth (Alternative to Client Secret)
|
|
100
|
+
|
|
101
|
+
Some tenants prohibit creating client secrets in Entra ID. Use certificate-based authentication instead — no client secret needed.
|
|
102
|
+
|
|
103
|
+
#### Generate a Self-Signed Certificate
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Generate a private key and self-signed certificate (valid for 1 year)
|
|
107
|
+
openssl req -x509 -newkey rsa:2048 -keyout bot-private-key.pem -out bot-cert.pem -days 365 -nodes -subj "/CN=minions-bot"
|
|
108
|
+
|
|
109
|
+
# Verify the certificate
|
|
110
|
+
openssl x509 -in bot-cert.pem -text -noout
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This creates two files:
|
|
114
|
+
- `bot-cert.pem` — the public certificate (upload to Entra ID)
|
|
115
|
+
- `bot-private-key.pem` — the private key (keep secret, do not commit)
|
|
116
|
+
|
|
117
|
+
#### Upload the Certificate to Entra ID
|
|
118
|
+
|
|
119
|
+
1. In the [Azure Portal](https://portal.azure.com), go to **Entra ID** > **App registrations**.
|
|
120
|
+
2. Find and click the app registration for your bot (same App ID from Step 3).
|
|
121
|
+
3. Click **Certificates & secrets** in the left sidebar.
|
|
122
|
+
4. Click the **Certificates** tab, then **Upload certificate**.
|
|
123
|
+
5. Select your `bot-cert.pem` file and click **Add**.
|
|
124
|
+
|
|
125
|
+
#### Configure Minions for Certificate Auth
|
|
126
|
+
|
|
127
|
+
Use certificate fields instead of `appPassword` in your `config.json`:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"teams": {
|
|
132
|
+
"enabled": true,
|
|
133
|
+
"appId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
134
|
+
"certPath": "/path/to/bot-cert.pem",
|
|
135
|
+
"privateKeyPath": "/path/to/bot-private-key.pem",
|
|
136
|
+
"tenantId": "your-azure-ad-tenant-id",
|
|
137
|
+
"notifyEvents": ["pr-merged", "agent-completed", "plan-completed", "agent-failed"],
|
|
138
|
+
"ccMirror": true,
|
|
139
|
+
"inboxPollInterval": 15000
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
> **Finding your Tenant ID:** In the Azure Portal, go to **Entra ID** > **Overview**. The **Tenant ID** is listed under Basic Information.
|
|
145
|
+
|
|
146
|
+
> When both `appPassword` and cert fields are configured, certificate auth takes precedence.
|
|
147
|
+
|
|
96
148
|
## Step 4: Set the Messaging Endpoint
|
|
97
149
|
|
|
98
150
|
The messaging endpoint is the URL that Azure Bot Framework sends incoming messages to. It must point to your Minions dashboard's `/api/bot` route.
|
package/engine/preflight.js
CHANGED
|
@@ -193,14 +193,20 @@ function doctor(minionsHome) {
|
|
|
193
193
|
runtimeResults.push({ name: 'Agents configured', ok: false, message: 'no agents in config.json' });
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
// Check Teams integration
|
|
196
|
+
// Check Teams integration — supports client secret OR certificate auth
|
|
197
197
|
const teams = config.teams;
|
|
198
198
|
if (teams && teams.enabled === true) {
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
const hasSecret = !!teams.appId && !!teams.appPassword;
|
|
200
|
+
const hasCert = !!teams.appId && !!teams.certPath && !!teams.privateKeyPath && !!teams.tenantId;
|
|
201
|
+
if (!hasSecret && !hasCert) {
|
|
202
|
+
const missing = [
|
|
203
|
+
!teams.appId && 'appId',
|
|
204
|
+
!teams.appPassword && !teams.certPath && 'appPassword or certPath+privateKeyPath+tenantId',
|
|
205
|
+
].filter(Boolean).join(', ');
|
|
201
206
|
runtimeResults.push({ name: 'Teams integration', ok: 'warn', message: `enabled but missing: ${missing}` });
|
|
202
207
|
} else {
|
|
203
|
-
|
|
208
|
+
const authMode = hasCert ? 'certificate' : 'client secret';
|
|
209
|
+
runtimeResults.push({ name: 'Teams integration', ok: true, message: `configured (${authMode})` });
|
|
204
210
|
}
|
|
205
211
|
} else {
|
|
206
212
|
runtimeResults.push({ name: 'Teams integration', ok: 'warn', message: 'disabled — see docs/teams-setup.md' });
|
package/engine/shared.js
CHANGED
|
@@ -549,6 +549,8 @@ const ENGINE_DEFAULTS = {
|
|
|
549
549
|
buildFixGracePeriod: 600000, // 10min — wait for CI to run after build fix before re-dispatching
|
|
550
550
|
adoPollEnabled: true, // poll ADO PR status, comments, and reconciliation on each tick cycle
|
|
551
551
|
ghPollEnabled: true, // poll GitHub PR status, comments, and reconciliation on each tick cycle
|
|
552
|
+
adoPollStatusEvery: 6, // poll ADO PR build/review/merge status every N ticks (~6 min at default interval)
|
|
553
|
+
adoPollCommentsEvery: 12, // poll ADO PR human comments every N ticks (~12 min at default interval)
|
|
552
554
|
autoCompletePrs: false, // auto-merge PRs when builds green + review approved (opt-in)
|
|
553
555
|
prMergeMethod: 'squash', // merge method: squash, merge, rebase
|
|
554
556
|
ignoredCommentAuthors: [], // comments from these authors are auto-closed and never trigger fixes
|
|
@@ -556,11 +558,15 @@ const ENGINE_DEFAULTS = {
|
|
|
556
558
|
ccEffort: null, // effort level for CC/doc-chat (null, 'low', 'medium', 'high')
|
|
557
559
|
heartbeatTimeouts: {}, // populated after WORK_TYPE is defined (below)
|
|
558
560
|
ccMaxTurns: 50, // max tool-use turns for CC/doc-chat before CLI stops
|
|
559
|
-
// Teams integration — config.teams shape: { enabled, appId, appPassword, notifyEvents, ccMirror, inboxPollInterval }
|
|
561
|
+
// Teams integration — config.teams shape: { enabled, appId, appPassword, certPath, privateKeyPath, tenantId, notifyEvents, ccMirror, inboxPollInterval }
|
|
562
|
+
// Auth modes: (1) appId + appPassword (client secret), or (2) appId + certPath + privateKeyPath + tenantId (certificate)
|
|
560
563
|
teams: {
|
|
561
564
|
enabled: false,
|
|
562
565
|
appId: '',
|
|
563
566
|
appPassword: '',
|
|
567
|
+
certPath: '', // PEM certificate file path (certificate auth)
|
|
568
|
+
privateKeyPath: '', // PEM private key file path (certificate auth)
|
|
569
|
+
tenantId: '', // Azure AD tenant ID (required for certificate auth)
|
|
564
570
|
notifyEvents: ['pr-merged', 'agent-completed', 'plan-completed', 'agent-failed'],
|
|
565
571
|
ccMirror: true,
|
|
566
572
|
inboxPollInterval: 15000,
|
package/engine/teams.js
CHANGED
|
@@ -8,7 +8,7 @@ const path = require('path');
|
|
|
8
8
|
const shared = require('./shared');
|
|
9
9
|
const queries = require('./queries');
|
|
10
10
|
|
|
11
|
-
const { log, safeJson, mutateJsonFileLocked, ENGINE_DEFAULTS } = shared;
|
|
11
|
+
const { log, safeRead, safeJson, mutateJsonFileLocked, ENGINE_DEFAULTS } = shared;
|
|
12
12
|
const { ENGINE_DIR, getConfig } = queries;
|
|
13
13
|
const cards = require('./teams-cards');
|
|
14
14
|
|
|
@@ -50,10 +50,16 @@ function getTeamsConfig() {
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Returns true if Teams integration is enabled and has required credentials.
|
|
53
|
+
* Supports two auth modes:
|
|
54
|
+
* (1) Client secret: appId + appPassword
|
|
55
|
+
* (2) Certificate: appId + certPath + privateKeyPath + tenantId
|
|
53
56
|
*/
|
|
54
57
|
function isTeamsEnabled() {
|
|
55
58
|
const cfg = getTeamsConfig();
|
|
56
|
-
|
|
59
|
+
if (cfg.enabled !== true || !cfg.appId) return false;
|
|
60
|
+
const hasSecret = !!cfg.appPassword;
|
|
61
|
+
const hasCert = !!cfg.certPath && !!cfg.privateKeyPath && !!cfg.tenantId;
|
|
62
|
+
return hasSecret || hasCert;
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
// Cached adapter instance — created once per process
|
|
@@ -62,6 +68,9 @@ let _adapter = null;
|
|
|
62
68
|
/**
|
|
63
69
|
* Create and return a BotFrameworkAdapter instance.
|
|
64
70
|
* Returns null when Teams is disabled or botbuilder is not installed.
|
|
71
|
+
* Supports two auth modes:
|
|
72
|
+
* (1) Client secret: uses ConfigurationBotFrameworkAuthentication with appPassword
|
|
73
|
+
* (2) Certificate: uses CertificateServiceClientCredentialsFactory with PEM cert + key
|
|
65
74
|
*/
|
|
66
75
|
function createAdapter() {
|
|
67
76
|
if (_adapter) return _adapter;
|
|
@@ -75,15 +84,43 @@ function createAdapter() {
|
|
|
75
84
|
if (!botbuilder) return null;
|
|
76
85
|
|
|
77
86
|
const cfg = getTeamsConfig();
|
|
87
|
+
const useCert = !!cfg.certPath && !!cfg.privateKeyPath && !!cfg.tenantId;
|
|
88
|
+
|
|
78
89
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
if (useCert) {
|
|
91
|
+
let connector;
|
|
92
|
+
try {
|
|
93
|
+
connector = require('botframework-connector');
|
|
94
|
+
} catch {
|
|
95
|
+
log('warn', 'botframework-connector not installed — certificate auth unavailable. Install via: npm install botframework-connector');
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const cert = safeRead(cfg.certPath);
|
|
99
|
+
const privateKey = safeRead(cfg.privateKeyPath);
|
|
100
|
+
if (!cert || !privateKey) {
|
|
101
|
+
log('warn', `Teams cert auth failed — could not read cert (${cfg.certPath}) or key (${cfg.privateKeyPath})`);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const credentialsFactory = new connector.CertificateServiceClientCredentialsFactory(
|
|
105
|
+
cfg.appId, cert, privateKey, cfg.tenantId
|
|
106
|
+
);
|
|
107
|
+
_adapter = new botbuilder.CloudAdapter(
|
|
108
|
+
new botbuilder.ConfigurationBotFrameworkAuthentication(
|
|
109
|
+
{ MicrosoftAppId: cfg.appId, MicrosoftAppType: 'SingleTenant', MicrosoftAppTenantId: cfg.tenantId },
|
|
110
|
+
credentialsFactory
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
log('info', 'Teams adapter created (certificate auth)');
|
|
114
|
+
} else {
|
|
115
|
+
_adapter = new botbuilder.CloudAdapter(
|
|
116
|
+
new botbuilder.ConfigurationBotFrameworkAuthentication({
|
|
117
|
+
MicrosoftAppId: cfg.appId,
|
|
118
|
+
MicrosoftAppPassword: cfg.appPassword,
|
|
119
|
+
MicrosoftAppType: 'SingleTenant',
|
|
120
|
+
})
|
|
121
|
+
);
|
|
122
|
+
log('info', 'Teams adapter created (client secret auth)');
|
|
123
|
+
}
|
|
87
124
|
return _adapter;
|
|
88
125
|
} catch (err) {
|
|
89
126
|
log('warn', `Teams adapter creation failed: ${err.message}`);
|
package/engine.js
CHANGED
|
@@ -3122,11 +3122,13 @@ async function tickInner() {
|
|
|
3122
3122
|
|
|
3123
3123
|
const adoPollEnabled = config.engine?.adoPollEnabled ?? DEFAULTS.adoPollEnabled;
|
|
3124
3124
|
const ghPollEnabled = config.engine?.ghPollEnabled ?? DEFAULTS.ghPollEnabled;
|
|
3125
|
+
const adoPollStatusEvery = Math.max(1, Number(config.engine?.adoPollStatusEvery) || DEFAULTS.adoPollStatusEvery);
|
|
3126
|
+
const adoPollCommentsEvery = Math.max(1, Number(config.engine?.adoPollCommentsEvery) || DEFAULTS.adoPollCommentsEvery);
|
|
3125
3127
|
|
|
3126
|
-
// 2.6. Poll PR status: build, review, merge (every
|
|
3128
|
+
// 2.6. Poll PR status: build, review, merge (every adoPollStatusEvery ticks, default ~6 minutes)
|
|
3127
3129
|
// Awaited so PR state is consistent before discoverWork reads it
|
|
3128
3130
|
// Also re-polls early if previous tick had ADO auth failures (stale build status recovery)
|
|
3129
|
-
if (tickCount %
|
|
3131
|
+
if (tickCount % adoPollStatusEvery === 0 || needsAdoPollRetry()) {
|
|
3130
3132
|
if (adoPollEnabled) {
|
|
3131
3133
|
try { await pollPrStatus(config); } catch (err) { log('warn', `ADO PR status poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
|
|
3132
3134
|
}
|
|
@@ -3152,16 +3154,17 @@ async function tickInner() {
|
|
|
3152
3154
|
} catch (err) { log('warn', `Plan completion check error: ${err?.message || err}`); }
|
|
3153
3155
|
}
|
|
3154
3156
|
|
|
3155
|
-
// 2.7. Poll PR threads for human comments (every
|
|
3156
|
-
if (tickCount %
|
|
3157
|
+
// 2.7. Poll PR threads for human comments (every adoPollCommentsEvery ticks, default ~12 minutes)
|
|
3158
|
+
if (tickCount % adoPollCommentsEvery === 0) {
|
|
3157
3159
|
if (adoPollEnabled) {
|
|
3158
3160
|
try { await pollPrHumanComments(config); } catch (err) { log('warn', `ADO PR comment poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
|
|
3159
|
-
try { await reconcilePrs(config); } catch (err) { log('warn', `ADO PR reconciliation error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
|
|
3160
3161
|
}
|
|
3161
3162
|
if (ghPollEnabled) {
|
|
3162
3163
|
try { await ghPollPrHumanComments(config); } catch (err) { log('warn', `GitHub PR comment poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
|
|
3163
|
-
try { await ghReconcilePrs(config); } catch (err) { log('warn', `GitHub PR reconciliation error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
|
|
3164
3164
|
}
|
|
3165
|
+
// Reconciliation runs regardless of poll flags — it's a recovery sweep, not a convenience poll
|
|
3166
|
+
try { await reconcilePrs(config); } catch (err) { log('warn', `ADO PR reconciliation error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
|
|
3167
|
+
try { await ghReconcilePrs(config); } catch (err) { log('warn', `GitHub PR reconciliation error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
|
|
3165
3168
|
}
|
|
3166
3169
|
|
|
3167
3170
|
// 2.9. Stalled dispatch detection — auto-retry failed items blocking the graph (every 20 ticks = ~10 min)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.936",
|
|
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"
|