@yemi33/minions 0.1.556 → 0.1.557

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,11 +1,12 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.556 (2026-04-07)
3
+ ## 0.1.557 (2026-04-08)
4
4
 
5
5
  ### Features
6
6
  - add pin/unpin button to inbox and KB document modals
7
7
 
8
8
  ### Fixes
9
+ - add max retry cap and escalation for build fix cycles (#497)
9
10
  - defer dispatched status until spawnAgent succeeds + add _any_ routing token (#482)
10
11
  - move pin button to modal header next to Copy button
11
12
  - fetch actual build error logs instead of just reason string (#489)
@@ -12,8 +12,8 @@ function prRow(pr) {
12
12
  const reviewSource = sq.status || effectiveReviewStatus || 'pending';
13
13
  const reviewClass = reviewSource === 'approved' ? 'approved' : (reviewSource === 'changes-requested' || reviewSource === 'rejected') ? 'rejected' : reviewSource === 'waiting' ? 'building' : 'draft';
14
14
  const reviewLabel = sq.status === 'waiting' ? 'reviewing (minions)' : sq.status ? sq.status + ' (minions)' : (effectiveReviewStatus || 'pending');
15
- const buildClass = pr._buildStatusStale ? 'build-stale' : pr.buildStatus === 'passing' ? 'build-pass' : pr.buildStatus === 'failing' ? 'build-fail' : pr.buildStatus === 'running' ? 'building' : 'no-build';
16
- const buildLabel = (pr.buildStatus || 'none') + (pr._buildStatusStale ? ' (stale)' : '');
15
+ const buildClass = pr.buildFixEscalated ? 'build-escalated' : pr._buildStatusStale ? 'build-stale' : pr.buildStatus === 'passing' ? 'build-pass' : pr.buildStatus === 'failing' ? 'build-fail' : pr.buildStatus === 'running' ? 'building' : 'no-build';
16
+ const buildLabel = pr.buildFixEscalated ? 'escalated (' + (pr.buildFixAttempts || '?') + ' fixes)' : (pr.buildStatus || 'none') + (pr._buildStatusStale ? ' (stale)' : '');
17
17
  const statusClass = pr.status === 'merged' ? 'merged' : pr.status === 'abandoned' ? 'rejected' : pr.status === 'active' ? 'active' : 'draft';
18
18
  const statusLabel = pr.status || 'active';
19
19
  const url = pr.url || '#';
@@ -257,6 +257,7 @@
257
257
  .pr-badge.build-fail { background: rgba(248,81,73,0.15); color: var(--red); border: 1px solid var(--red); }
258
258
  .pr-badge.no-build { background: var(--surface); color: var(--muted); border: 1px solid var(--border); }
259
259
  .pr-badge.build-stale { background: rgba(210,153,34,0.15); color: var(--orange); border: 1px dashed var(--orange); }
260
+ .pr-badge.build-escalated { background: rgba(248,81,73,0.25); color: var(--red); border: 2px solid var(--red); font-weight: 600; }
260
261
  .error-details-btn { font-size: var(--text-xs); padding: var(--space-1) var(--space-3); margin-left: var(--space-2); background: rgba(248,81,73,0.15); color: var(--red); border: 1px solid var(--red); border-radius: var(--radius-lg); cursor: pointer; font-weight: 600; text-transform: uppercase; letter-spacing: 0.3px; }
261
262
  .error-details-btn:hover { background: rgba(248,81,73,0.3); }
262
263
  .pr-empty { color: var(--muted); font-style: italic; font-size: var(--text-md); padding: var(--space-6) 0; }
package/dashboard.js CHANGED
@@ -3533,6 +3533,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3533
3533
  idleAlertMinutes: [1], shutdownTimeout: [30000], restartGracePeriod: [60000],
3534
3534
  meetingRoundTimeout: [60000],
3535
3535
  versionCheckInterval: [60000],
3536
+ maxBuildFixAttempts: [1, 10],
3536
3537
  };
3537
3538
  for (const [key, [min, max]] of Object.entries(numericFields)) {
3538
3539
  if (e[key] !== undefined) {
package/engine/ado.js CHANGED
@@ -254,6 +254,8 @@ async function pollPrStatus(config) {
254
254
  delete pr.buildFailReason;
255
255
  delete pr.buildErrorLog;
256
256
  delete pr._buildFailNotified;
257
+ delete pr.buildFixAttempts;
258
+ delete pr.buildFixEscalated;
257
259
  }
258
260
  await engine().handlePostMerge(pr, project, config, newStatus);
259
261
  }
@@ -365,6 +367,8 @@ async function pollPrStatus(config) {
365
367
  if (buildStatus !== 'failing') {
366
368
  delete pr._buildFailNotified;
367
369
  delete pr.buildErrorLog;
370
+ // Reset build fix retry counter on recovery — allows fresh auto-fix cycles if build breaks again
371
+ if (pr.buildFixAttempts) { delete pr.buildFixAttempts; delete pr.buildFixEscalated; }
368
372
  }
369
373
  updated = true;
370
374
 
package/engine/github.js CHANGED
@@ -294,6 +294,8 @@ async function pollPrStatus(config) {
294
294
  delete pr.buildFailReason;
295
295
  delete pr.buildErrorLog;
296
296
  delete pr._buildFailNotified;
297
+ delete pr.buildFixAttempts;
298
+ delete pr.buildFixEscalated;
297
299
  }
298
300
  await engine().handlePostMerge(pr, project, config, newStatus);
299
301
  }
@@ -393,6 +395,8 @@ async function pollPrStatus(config) {
393
395
  if (buildStatus !== 'failing') {
394
396
  delete pr._buildFailNotified;
395
397
  delete pr.buildErrorLog;
398
+ // Reset build fix retry counter on recovery — allows fresh auto-fix cycles if build breaks again
399
+ if (pr.buildFixAttempts) { delete pr.buildFixAttempts; delete pr.buildFixEscalated; }
396
400
  }
397
401
  updated = true;
398
402
 
package/engine/shared.js CHANGED
@@ -525,6 +525,7 @@ const ENGINE_DEFAULTS = {
525
525
  logBufferSize: 50, // flush immediately when buffer exceeds this many entries
526
526
  lockRetries: 2, // retry lock acquisition this many times after initial timeout (total attempts = 1 + lockRetries)
527
527
  lockRetryBackoffMs: 500, // base backoff between lock retries (doubles each attempt: 500ms, 1s, 2s, ...)
528
+ maxBuildFixAttempts: 3, // max consecutive auto-fix dispatch cycles per PR before escalation to human
528
529
  };
529
530
 
530
531
  // ─── Status & Type Constants ─────────────────────────────────────────────────
package/engine.js CHANGED
@@ -1498,6 +1498,29 @@ async function discoverFromPrs(config, project) {
1498
1498
 
1499
1499
  // PRs with build failures — route to author (has session context from implementing)
1500
1500
  if (pr.status === PR_STATUS.ACTIVE && pr.buildStatus === 'failing') {
1501
+ const maxBuildFix = config.engine?.maxBuildFixAttempts ?? ENGINE_DEFAULTS.maxBuildFixAttempts;
1502
+
1503
+ // Check if max retry cap reached — escalate to human instead of dispatching another fix
1504
+ if ((pr.buildFixAttempts || 0) >= maxBuildFix) {
1505
+ if (!pr.buildFixEscalated) {
1506
+ writeInboxAlert(`build-fix-escalated-${pr.agent || 'unassigned'}-${pr.id}`,
1507
+ `# Build Fix Escalation\n\n` +
1508
+ `**PR ${pr.id}** on branch \`${pr.branch || 'unknown'}\` has failed **${pr.buildFixAttempts}** consecutive auto-fix attempts.\n` +
1509
+ `**Last failure:** ${pr.buildFailReason || 'Check CI pipeline for details'}\n\n` +
1510
+ `Auto-fix dispatch has been suspended. Please investigate manually.\n`
1511
+ );
1512
+ try {
1513
+ const prPath = projectPrPath(project);
1514
+ mutatePullRequests(prPath, prs => {
1515
+ const target = prs.find(p => p.id === pr.id);
1516
+ if (target) target.buildFixEscalated = true;
1517
+ });
1518
+ } catch (e) { log('warn', 'mark build fix escalated: ' + e.message); }
1519
+ log('warn', `PR ${pr.id}: build fix escalated after ${pr.buildFixAttempts} attempts — suspending auto-dispatch`);
1520
+ }
1521
+ continue;
1522
+ }
1523
+
1501
1524
  const key = `build-fix-${project?.name || 'default'}-${pr.id}`;
1502
1525
  if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
1503
1526
  const agentId = resolveAgent('fix', config, pr.agent);
@@ -1512,7 +1535,17 @@ async function discoverFromPrs(config, project) {
1512
1535
  pr_id: pr.id, pr_branch: pr.branch || '',
1513
1536
  review_note: reviewNote,
1514
1537
  }, `Fix build failure on PR ${pr.id}`, { dispatchKey: key, source: 'pr', pr, branch: pr.branch, project: projMeta });
1515
- if (item) { newWork.push(item); setCooldown(key); }
1538
+ if (item) {
1539
+ newWork.push(item); setCooldown(key);
1540
+ // Increment build fix attempts counter
1541
+ try {
1542
+ const prPath = projectPrPath(project);
1543
+ mutatePullRequests(prPath, prs => {
1544
+ const target = prs.find(p => p.id === pr.id);
1545
+ if (target) target.buildFixAttempts = (target.buildFixAttempts || 0) + 1;
1546
+ });
1547
+ } catch (e) { log('warn', 'increment build fix attempts: ' + e.message); }
1548
+ }
1516
1549
 
1517
1550
  // Notify the author agent about the build failure
1518
1551
  if (pr.agent && !pr._buildFailNotified) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.556",
3
+ "version": "0.1.557",
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"