@yemi33/minions 0.1.1027 → 0.1.1028
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 +33 -0
- package/dashboard.js +129 -88
- package/engine/cleanup.js +12 -10
- package/engine/shared.js +2 -1
- package/engine.js +15 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1028 (2026-04-16)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
- route implement items to dedicated implement playbook (#1115)
|
|
7
|
+
|
|
8
|
+
### Fixes
|
|
9
|
+
- prevent test from corrupting live meeting state
|
|
10
|
+
- scheduler enabled field falsy check treats undefined as disabled (#1160)
|
|
11
|
+
- update branch-dedup tests to use canonical PR IDs after merge with master (#1146)
|
|
12
|
+
- auto-review not firing for manually-linked PRs with autoObserve=true
|
|
13
|
+
- mark PR abandoned on 404 instead of silently retrying each tick
|
|
14
|
+
- replace undefined PROJECTS with config.projects in checkWatches (#1108)
|
|
15
|
+
- write permission for publish workflow
|
|
16
|
+
- run tests inline and post check runs for publish PRs
|
|
17
|
+
- add maxBuffer to all GitHub CLI spawns + paginate PR list (#1130)
|
|
18
|
+
- add required CI checks for PRs + update publish for auto-merge
|
|
19
|
+
- revert to PR merge now that stale status check is removed
|
|
20
|
+
- guard live review check against undefined vote/state values (#1132)
|
|
21
|
+
- push version bump directly to master instead of via PR
|
|
22
|
+
- add push-triggered CI for chore/publish branches
|
|
23
|
+
- publish workflow chore PRs failing to merge
|
|
24
|
+
- harden KB ordering
|
|
25
|
+
- harden audited state transitions
|
|
26
|
+
|
|
27
|
+
### Other
|
|
28
|
+
- [E2E] Dashboard & engine perf: gzip cache, tiered status, lock backoff, polling parallelization (#1174)
|
|
29
|
+
- Fix doc chat session isolation
|
|
30
|
+
- test(pipeline): add unit tests for CRUD, stage execution, run lifecycle (#1162)
|
|
31
|
+
- chore: test publish after removing stale status check
|
|
32
|
+
- chore: trigger publish test
|
|
33
|
+
- chore: test publish workflow fix
|
|
34
|
+
- chore: trigger publish workflow test
|
|
35
|
+
|
|
3
36
|
## 0.1.1027 (2026-04-16)
|
|
4
37
|
|
|
5
38
|
### Features
|
package/dashboard.js
CHANGED
|
@@ -171,7 +171,14 @@ function getVerifyGuides() {
|
|
|
171
171
|
function getArchivedPrds() { return []; }
|
|
172
172
|
function getEngineState() { return queries.getControl(); }
|
|
173
173
|
|
|
174
|
+
let _worktreeCountCache = 0;
|
|
175
|
+
let _worktreeCountCacheTs = 0;
|
|
176
|
+
|
|
174
177
|
function _countWorktrees() {
|
|
178
|
+
const now = Date.now();
|
|
179
|
+
if (_worktreeCountCacheTs && (now - _worktreeCountCacheTs) < shared.ENGINE_DEFAULTS.worktreeCountCacheTtl) {
|
|
180
|
+
return _worktreeCountCache;
|
|
181
|
+
}
|
|
175
182
|
try {
|
|
176
183
|
const config = queries.getConfig();
|
|
177
184
|
const projects = shared.getProjects(config);
|
|
@@ -199,6 +206,8 @@ function _countWorktrees() {
|
|
|
199
206
|
}
|
|
200
207
|
} catch {}
|
|
201
208
|
}
|
|
209
|
+
_worktreeCountCache = count;
|
|
210
|
+
_worktreeCountCacheTs = now;
|
|
202
211
|
return count;
|
|
203
212
|
} catch { return 0; }
|
|
204
213
|
}
|
|
@@ -302,13 +311,20 @@ function parsePinnedEntries(content) {
|
|
|
302
311
|
return entries;
|
|
303
312
|
}
|
|
304
313
|
|
|
314
|
+
// Two-tier status cache: fast state (10s) for frequently-changing data, slow state (60s) for rarely-changing data.
|
|
315
|
+
// Combined into _statusCache for API/SSE consumers — no API contract change.
|
|
316
|
+
let _fastState = null;
|
|
317
|
+
let _fastStateTs = 0;
|
|
318
|
+
const FAST_STATE_TTL = 10000; // 10s — dispatch, agents, metrics, work items, etc.
|
|
319
|
+
let _slowState = null;
|
|
320
|
+
let _slowStateTs = 0;
|
|
321
|
+
const SLOW_STATE_TTL = 60000; // 60s — skills, PRDs, pinned, version, projects, etc.
|
|
305
322
|
let _statusCache = null;
|
|
306
323
|
let _statusCacheJson = null; // cached JSON.stringify(_statusCache) — avoids double-serialization for SSE
|
|
307
|
-
let
|
|
308
|
-
const STATUS_CACHE_TTL = 10000; // 10s — reduces expensive aggregation frequency; mutations call invalidateStatusCache()
|
|
324
|
+
let _statusCacheGzip = null; // pre-computed gzip of _statusCacheJson — avoids per-request gzipSync
|
|
309
325
|
const _statusStreamClients = new Set();
|
|
310
326
|
let _statusPushTimer = null;
|
|
311
|
-
let
|
|
327
|
+
let _lastStatusPushRef = null; // last JSON string reference pushed to SSE — O(1) change detection
|
|
312
328
|
|
|
313
329
|
// mtime-based cache invalidation — skip full rebuild if no tracked files changed
|
|
314
330
|
const _mtimeTrackedFiles = () => {
|
|
@@ -348,8 +364,12 @@ function _mtimesChanged(prev, curr) {
|
|
|
348
364
|
}
|
|
349
365
|
|
|
350
366
|
function invalidateStatusCache() {
|
|
367
|
+
_fastState = null;
|
|
368
|
+
_fastStateTs = 0;
|
|
369
|
+
// Slow state continues on its own TTL — not invalidated by mutations
|
|
351
370
|
_statusCache = null;
|
|
352
371
|
_statusCacheJson = null;
|
|
372
|
+
_statusCacheGzip = null;
|
|
353
373
|
// Push to SSE clients (debounced 500ms to avoid flooding during batch mutations)
|
|
354
374
|
if (_statusPushTimer) return;
|
|
355
375
|
_statusPushTimer = setTimeout(() => {
|
|
@@ -364,89 +384,110 @@ function invalidateStatusCache() {
|
|
|
364
384
|
|
|
365
385
|
function getStatus() {
|
|
366
386
|
const now = Date.now();
|
|
367
|
-
|
|
368
|
-
|
|
387
|
+
|
|
388
|
+
// Fast state: 10s TTL with mtime-based validation for early exit
|
|
389
|
+
let fastStale = !_fastState || (now - _fastStateTs) >= FAST_STATE_TTL;
|
|
390
|
+
if (!fastStale) {
|
|
391
|
+
// Within TTL — check mtimes for early return (skip rebuild if no tracked files changed)
|
|
369
392
|
const currMtimes = _getMtimes();
|
|
370
|
-
if (
|
|
393
|
+
if (_mtimesChanged(_lastMtimes, currMtimes)) fastStale = true;
|
|
371
394
|
}
|
|
372
395
|
|
|
373
|
-
//
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
_statusCache
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
const {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
396
|
+
// Slow state: 60s TTL, pure TTL (no mtime check — these files change rarely)
|
|
397
|
+
const slowStale = !_slowState || (now - _slowStateTs) >= SLOW_STATE_TTL;
|
|
398
|
+
|
|
399
|
+
// If nothing stale, return cached merged result
|
|
400
|
+
if (!fastStale && !slowStale && _statusCache) return _statusCache;
|
|
401
|
+
|
|
402
|
+
// Rebuild fast state (frequently-changing data: ~12-15 reads)
|
|
403
|
+
if (fastStale) {
|
|
404
|
+
// Reload config on fast-state miss — picks up external changes (minions init, minions add)
|
|
405
|
+
reloadConfig();
|
|
406
|
+
_fastState = {
|
|
407
|
+
agents: getAgents(),
|
|
408
|
+
inbox: getInbox(),
|
|
409
|
+
notes: getNotesWithMeta(),
|
|
410
|
+
pullRequests: getPullRequests(),
|
|
411
|
+
engine: { ...getEngineState(), worktreeCount: _countWorktrees() },
|
|
412
|
+
adoThrottle: ado.getAdoThrottleState(),
|
|
413
|
+
ghThrottle: gh.getGhThrottleState(),
|
|
414
|
+
dispatch: getDispatchQueue(),
|
|
415
|
+
engineLog: getEngineLog(),
|
|
416
|
+
metrics: getMetrics(),
|
|
417
|
+
workItems: getWorkItems(),
|
|
418
|
+
watches: watchesMod.getWatches(),
|
|
419
|
+
meetings: (() => { try { return require('./engine/meeting').getMeetings(); } catch { return []; } })(),
|
|
420
|
+
};
|
|
421
|
+
_fastStateTs = now;
|
|
422
|
+
_lastMtimes = _getMtimes();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Rebuild slow state (rarely-changing data: ~8-15 reads, 60s TTL)
|
|
426
|
+
if (slowStale) {
|
|
427
|
+
const prdInfo = getPrdInfo();
|
|
428
|
+
_slowState = {
|
|
429
|
+
prdProgress: prdInfo.progress,
|
|
430
|
+
prd: prdInfo.status,
|
|
431
|
+
verifyGuides: getVerifyGuides(),
|
|
432
|
+
archivedPrds: getArchivedPrds(),
|
|
433
|
+
skills: getSkills(),
|
|
434
|
+
mcpServers: getMcpServers(),
|
|
435
|
+
schedules: (() => {
|
|
436
|
+
const scheds = CONFIG.schedules || [];
|
|
437
|
+
const runs = shared.safeJson(path.join(MINIONS_DIR, 'engine', 'schedule-runs.json')) || {};
|
|
438
|
+
return scheds.map(s => {
|
|
439
|
+
const runEntry = runs[s.id];
|
|
440
|
+
// Backward compat: runEntry can be a string (old format) or object (new format with back-references)
|
|
441
|
+
const _lastRun = typeof runEntry === 'string' ? runEntry : (runEntry?.lastRun || runEntry?.lastCompletedAt || null);
|
|
442
|
+
const extra = typeof runEntry === 'object' && runEntry ? { _lastWorkItemId: runEntry.lastWorkItemId, _lastResult: runEntry.lastResult, _lastCompletedAt: runEntry.lastCompletedAt } : {};
|
|
443
|
+
return { ...s, _lastRun, ...extra };
|
|
444
|
+
});
|
|
445
|
+
})(),
|
|
446
|
+
pipelines: (() => { try { const pl = require('./engine/pipeline'); return pl.getPipelines().map(p => ({ ...p, runs: (pl.getPipelineRuns()[p.id] || []).slice(-5) })); } catch { return []; } })(),
|
|
447
|
+
pinned: (() => { try { return parsePinnedEntries(safeRead(path.join(MINIONS_DIR, 'pinned.md'))); } catch { return []; } })(),
|
|
448
|
+
projects: PROJECTS.map(p => ({ name: p.name, path: p.localPath, description: p.description || '' })),
|
|
449
|
+
autoMode: {
|
|
450
|
+
approvePlans: !!CONFIG.engine?.autoApprovePlans,
|
|
451
|
+
decompose: CONFIG.engine?.autoDecompose !== false,
|
|
452
|
+
tempAgents: !!CONFIG.engine?.allowTempAgents,
|
|
453
|
+
inboxThreshold: CONFIG.engine?.inboxConsolidateThreshold || shared.ENGINE_DEFAULTS.inboxConsolidateThreshold,
|
|
454
|
+
ccModel: CONFIG.engine?.ccModel || shared.ENGINE_DEFAULTS.ccModel,
|
|
455
|
+
ccEffort: CONFIG.engine?.ccEffort || shared.ENGINE_DEFAULTS.ccEffort,
|
|
456
|
+
},
|
|
457
|
+
initialized: !!(CONFIG.agents && Object.keys(CONFIG.agents).length > 0),
|
|
458
|
+
installId: safeRead(path.join(MINIONS_DIR, '.install-id')).trim() || null,
|
|
459
|
+
version: (() => {
|
|
460
|
+
const engine = getEngineState();
|
|
461
|
+
const { diskVersion, diskCommit, isGitRepo } = getDiskVersion();
|
|
462
|
+
const engineStale = !!(engine.codeVersion && diskVersion && engine.codeVersion !== diskVersion) ||
|
|
463
|
+
!!(engine.codeCommit && diskCommit && engine.codeCommit !== diskCommit);
|
|
464
|
+
const dashboardStale = !!(diskVersion && _dashboardVersion.codeVersion && diskVersion !== _dashboardVersion.codeVersion) ||
|
|
465
|
+
!!(diskCommit && _dashboardVersion.codeCommit && diskCommit !== _dashboardVersion.codeCommit);
|
|
466
|
+
return {
|
|
467
|
+
running: engine.codeVersion || null,
|
|
468
|
+
runningCommit: engine.codeCommit || null,
|
|
469
|
+
dashboardRunning: _dashboardVersion.codeVersion,
|
|
470
|
+
dashboardRunningCommit: _dashboardVersion.codeCommit,
|
|
471
|
+
dashboardStartedAt: _dashboardVersion.startedAt,
|
|
472
|
+
disk: diskVersion,
|
|
473
|
+
diskCommit,
|
|
474
|
+
engineStale,
|
|
475
|
+
dashboardStale,
|
|
476
|
+
stale: engineStale || dashboardStale,
|
|
477
|
+
latest: _npmVersionCache?.latest || null,
|
|
478
|
+
// Only show "update available" for npm installs (no git repo) — repo users manage their own updates
|
|
479
|
+
updateAvailable: !isGitRepo && !!(diskVersion && _npmVersionCache?.latest && _npmVersionCache.latest !== diskVersion && _compareVersions(_npmVersionCache.latest, diskVersion) > 0),
|
|
480
|
+
_npmCheckError: _npmVersionCache?.error || null,
|
|
481
|
+
};
|
|
482
|
+
})(),
|
|
483
|
+
};
|
|
484
|
+
_slowStateTs = now;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Merge both tiers — no API contract change
|
|
488
|
+
_statusCache = { ..._fastState, ..._slowState, timestamp: new Date().toISOString() };
|
|
448
489
|
_statusCacheJson = null; // invalidate cached JSON — will be lazily rebuilt by getStatusJson()
|
|
449
|
-
|
|
490
|
+
_statusCacheGzip = null;
|
|
450
491
|
return _statusCache;
|
|
451
492
|
}
|
|
452
493
|
|
|
@@ -455,6 +496,7 @@ function getStatusJson() {
|
|
|
455
496
|
getStatus(); // ensure _statusCache is fresh
|
|
456
497
|
if (!_statusCacheJson) {
|
|
457
498
|
_statusCacheJson = JSON.stringify(_statusCache);
|
|
499
|
+
_statusCacheGzip = zlib.gzipSync(_statusCacheJson); // pre-compute gzip once per cache rebuild
|
|
458
500
|
}
|
|
459
501
|
return _statusCacheJson;
|
|
460
502
|
}
|
|
@@ -463,9 +505,8 @@ function getStatusJson() {
|
|
|
463
505
|
setInterval(() => {
|
|
464
506
|
if (_statusStreamClients.size === 0) return;
|
|
465
507
|
const data = getStatusJson();
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
_lastStatusHash = hash;
|
|
508
|
+
if (data === _lastStatusPushRef) return; // O(1) reference comparison — new string ref means content changed
|
|
509
|
+
_lastStatusPushRef = data;
|
|
469
510
|
for (const res of _statusStreamClients) {
|
|
470
511
|
try { res.write('data: ' + data + '\n\n'); } catch { _statusStreamClients.delete(res); }
|
|
471
512
|
}
|
|
@@ -4222,15 +4263,15 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4222
4263
|
|
|
4223
4264
|
async function handleStatus(req, res) {
|
|
4224
4265
|
try {
|
|
4225
|
-
// Use pre-serialized JSON
|
|
4266
|
+
// Use pre-serialized JSON and pre-computed gzip buffer — zero per-request compression
|
|
4226
4267
|
const json = getStatusJson();
|
|
4227
4268
|
res.setHeader('Content-Type', 'application/json');
|
|
4228
4269
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
4229
4270
|
res.statusCode = 200;
|
|
4230
4271
|
const ae = req && req.headers && req.headers['accept-encoding'] || '';
|
|
4231
|
-
if (ae.includes('gzip') &&
|
|
4272
|
+
if (ae.includes('gzip') && _statusCacheGzip) {
|
|
4232
4273
|
res.setHeader('Content-Encoding', 'gzip');
|
|
4233
|
-
res.end(
|
|
4274
|
+
res.end(_statusCacheGzip);
|
|
4234
4275
|
} else {
|
|
4235
4276
|
res.end(json);
|
|
4236
4277
|
}
|
package/engine/cleanup.js
CHANGED
|
@@ -442,22 +442,24 @@ function runCleanup(config, verbose = false) {
|
|
|
442
442
|
|
|
443
443
|
// 6a. Reconcile failed work items that have an attached PR (#407)
|
|
444
444
|
// If a work item is 'failed' but already has _pr, it should be 'done'.
|
|
445
|
+
// Uses mutateWorkItems() for locked atomic read-modify-write — prevents
|
|
446
|
+
// race conditions with concurrent engine/dashboard/lifecycle writers.
|
|
445
447
|
for (const project of projects) {
|
|
446
448
|
try {
|
|
447
449
|
const wiPath = projectWorkItemsPath(project);
|
|
448
|
-
const items = safeJson(wiPath) || [];
|
|
449
450
|
let reconciled = 0;
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
item.status
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
451
|
+
mutateWorkItems(wiPath, items => {
|
|
452
|
+
for (const item of items) {
|
|
453
|
+
if (item.status === shared.WI_STATUS.FAILED && item._pr) {
|
|
454
|
+
item.status = shared.WI_STATUS.DONE;
|
|
455
|
+
if (item.failReason) delete item.failReason;
|
|
456
|
+
if (item.failedAt) delete item.failedAt;
|
|
457
|
+
if (!item.completedAt) item.completedAt = shared.ts();
|
|
458
|
+
reconciled++;
|
|
459
|
+
}
|
|
457
460
|
}
|
|
458
|
-
}
|
|
461
|
+
});
|
|
459
462
|
if (reconciled > 0) {
|
|
460
|
-
safeWrite(wiPath, items);
|
|
461
463
|
log('info', `Reconciled ${reconciled} failed-with-PR item(s) → done in ${project.name}`);
|
|
462
464
|
}
|
|
463
465
|
} catch (e) { log('warn', 'reconcile failed-with-PR: ' + e.message); }
|
package/engine/shared.js
CHANGED
|
@@ -534,6 +534,7 @@ const ENGINE_DEFAULTS = {
|
|
|
534
534
|
worktreeCreateTimeout: 300000, // 5min for git worktree add on large Windows repos
|
|
535
535
|
worktreeCreateRetries: 1, // retry once on transient timeout/lock races
|
|
536
536
|
worktreeRoot: '../worktrees',
|
|
537
|
+
worktreeCountCacheTtl: 30000, // 30s — TTL for cached _countWorktrees() result in dashboard
|
|
537
538
|
idleAlertMinutes: 15,
|
|
538
539
|
fanOutTimeout: null, // falls back to agentTimeout
|
|
539
540
|
restartGracePeriod: 1200000, // 20min
|
|
@@ -554,7 +555,7 @@ const ENGINE_DEFAULTS = {
|
|
|
554
555
|
versionCheckInterval: 3600000, // 1 hour — how often to check npm for updates (ms)
|
|
555
556
|
logFlushInterval: 5000, // 5s — how often to flush buffered log entries to disk
|
|
556
557
|
logBufferSize: 50, // flush immediately when buffer exceeds this many entries
|
|
557
|
-
lockRetries:
|
|
558
|
+
lockRetries: 0, // no retries — single 5s timeout window with 25ms polling (200 attempts) is sufficient; stale lock recovery at 60s handles crashes
|
|
558
559
|
lockRetryBackoffMs: 500, // base backoff between lock retries (doubles each attempt: 500ms, 1s, 2s, ...)
|
|
559
560
|
maxBuildFixAttempts: 3, // max consecutive auto-fix dispatch cycles per PR before escalation to human
|
|
560
561
|
buildFixGracePeriod: 600000, // 10min — wait for CI to run after build fix before re-dispatching
|
package/engine.js
CHANGED
|
@@ -3229,16 +3229,19 @@ async function tickInner() {
|
|
|
3229
3229
|
// Awaited so PR state is consistent before discoverWork reads it
|
|
3230
3230
|
// Also re-polls early if previous tick had ADO auth failures (stale build status recovery)
|
|
3231
3231
|
if (tickCount % adoPollStatusEvery === 0 || needsAdoPollRetry()) {
|
|
3232
|
+
// Build promise array — enabled+unthrottled polls run concurrently via Promise.allSettled
|
|
3233
|
+
const statusPolls = [];
|
|
3232
3234
|
if (adoPollEnabled && !isAdoThrottled()) {
|
|
3233
|
-
|
|
3235
|
+
statusPolls.push(pollPrStatus(config).catch(err => { log('warn', `ADO PR status poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }));
|
|
3234
3236
|
} else if (adoPollEnabled && isAdoThrottled()) {
|
|
3235
3237
|
log('info', '[ado] PR status poll skipped — throttled');
|
|
3236
3238
|
}
|
|
3237
3239
|
if (ghPollEnabled && !isGhThrottled()) {
|
|
3238
|
-
|
|
3240
|
+
statusPolls.push(ghPollPrStatus(config).catch(err => { log('warn', `GitHub PR status poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }));
|
|
3239
3241
|
} else if (ghPollEnabled && isGhThrottled()) {
|
|
3240
3242
|
log('info', '[gh] PR status poll skipped — throttled');
|
|
3241
3243
|
}
|
|
3244
|
+
if (statusPolls.length) await Promise.allSettled(statusPolls);
|
|
3242
3245
|
try { await processPendingRebases(config); } catch (err) { log('warn', `Pending rebase processing error: ${err?.message || err}`); }
|
|
3243
3246
|
// Sync PR status back to PRD items (missing → done when active PR exists)
|
|
3244
3247
|
try { syncPrdFromPrs(config); } catch (err) { log('warn', `PRD sync error: ${err?.message || err}`); }
|
|
@@ -3260,19 +3263,25 @@ async function tickInner() {
|
|
|
3260
3263
|
|
|
3261
3264
|
// 2.7. Poll PR threads for human comments (every adoPollCommentsEvery ticks, default ~12 minutes)
|
|
3262
3265
|
if (tickCount % adoPollCommentsEvery === 0) {
|
|
3266
|
+
// Build promise array — enabled+unthrottled comment polls run concurrently via Promise.allSettled
|
|
3267
|
+
const commentPolls = [];
|
|
3263
3268
|
if (adoPollEnabled && !isAdoThrottled()) {
|
|
3264
|
-
|
|
3269
|
+
commentPolls.push(pollPrHumanComments(config).catch(err => { log('warn', `ADO PR comment poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }));
|
|
3265
3270
|
} else if (adoPollEnabled && isAdoThrottled()) {
|
|
3266
3271
|
log('info', '[ado] PR comment poll skipped — throttled');
|
|
3267
3272
|
}
|
|
3268
3273
|
if (ghPollEnabled && !isGhThrottled()) {
|
|
3269
|
-
|
|
3274
|
+
commentPolls.push(ghPollPrHumanComments(config).catch(err => { log('warn', `GitHub PR comment poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }));
|
|
3270
3275
|
} else if (ghPollEnabled && isGhThrottled()) {
|
|
3271
3276
|
log('info', '[gh] PR comment poll skipped — throttled');
|
|
3272
3277
|
}
|
|
3278
|
+
if (commentPolls.length) await Promise.allSettled(commentPolls);
|
|
3273
3279
|
// Reconciliation runs regardless of poll flags — it's a recovery sweep, not a convenience poll
|
|
3274
|
-
|
|
3275
|
-
|
|
3280
|
+
// Reconciliation also parallelized — ADO and GitHub reconciliation are independent
|
|
3281
|
+
const reconcilePolls = [];
|
|
3282
|
+
reconcilePolls.push(reconcilePrs(config).catch(err => { log('warn', `ADO PR reconciliation error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }));
|
|
3283
|
+
reconcilePolls.push(ghReconcilePrs(config).catch(err => { log('warn', `GitHub PR reconciliation error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }));
|
|
3284
|
+
await Promise.allSettled(reconcilePolls);
|
|
3276
3285
|
}
|
|
3277
3286
|
|
|
3278
3287
|
// 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.1028",
|
|
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"
|