@yemi33/minions 0.1.1105 → 0.1.1107

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.1105 (2026-04-18)
3
+ ## 0.1.1107 (2026-04-18)
4
4
 
5
5
  ### Features
6
6
  - seed realActivityMap at spawn time, stamp pid in live-output (#1200)
7
7
 
8
8
  ### Fixes
9
+ - preserve buildErrorLog through transient build states (#1232) (#1274)
9
10
  - annotate fast-exit empty-output failures with diagnostic hint (#1276)
10
11
  - invalidate PRD cache on pr-links.json change + guard aggregate PRs (#1220) (#1272)
11
12
  - pass --add-dir for minions + ~/.claude to agents (#1271)
@@ -23,6 +24,8 @@
23
24
  - harden settings save and migrate pr poll config
24
25
 
25
26
  ### Other
27
+ - Clarify PR poll labels
28
+ - Prevent modal opens on text selection
26
29
  - refactor: clarify settings page section structure and PR polling dependencies
27
30
  - refactor: extract _probeClaudePackage helper, use shared.log for spawn errors
28
31
  - Make work item descriptions scrollable
@@ -4,7 +4,7 @@ function renderAgents(agents) {
4
4
  agentData = agents;
5
5
  const grid = document.getElementById('agents-grid');
6
6
  grid.innerHTML = agents.map(a => `
7
- <div class="agent-card ${statusColor(a.status)}" onclick="openAgentDetail('${escHtml(a.id)}')">
7
+ <div class="agent-card ${statusColor(a.status)}" onclick="if(shouldIgnoreSelectionClick(event))return;openAgentDetail('${escHtml(a.id)}')">
8
8
  <div class="agent-card-header">
9
9
  <span class="agent-name"><span class="agent-emoji">${escHtml(a.emoji)}</span>${escHtml(a.name)}</span>
10
10
  <span class="status-badge ${escHtml(a.status)}">${escHtml(a.status)}</span>
@@ -48,7 +48,7 @@ function renderMeetings(meetings) {
48
48
  const dt = m.completedAt || m.createdAt;
49
49
  const timeStr = dt ? new Date(dt).toLocaleString(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }) : '';
50
50
 
51
- return '<div data-file="meetings/' + escHtml(m.id) + '.json" style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:8px;cursor:pointer;position:relative" onclick="openMeetingDetail(\'' + escHtml(m.id) + '\')">' +
51
+ return '<div data-file="meetings/' + escHtml(m.id) + '.json" style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:8px;cursor:pointer;position:relative" onclick="if(shouldIgnoreSelectionClick(event))return;openMeetingDetail(\'' + escHtml(m.id) + '\')">' +
52
52
  '<div style="display:flex;justify-content:space-between;align-items:center">' +
53
53
  '<strong style="font-size:13px">' + escHtml(m.title) + '</strong>' +
54
54
  '<div style="display:flex;align-items:center;gap:8px">' +
@@ -13,7 +13,7 @@ function renderPinned(entries) {
13
13
  el.innerHTML = entries.map((e, i) =>
14
14
  '<div class="pinned-card" data-file="pinned:' + escHtml(e.title) + '" style="padding:8px 12px;margin-bottom:6px;background:var(--surface2);border-left:3px solid ' +
15
15
  (e.level === 'critical' ? 'var(--red)' : e.level === 'warning' ? 'var(--yellow)' : 'var(--blue)') +
16
- ';border-radius:4px;cursor:pointer" onclick="openPinnedView(' + i + ')">' +
16
+ ';border-radius:4px;cursor:pointer" onclick="if(shouldIgnoreSelectionClick(event))return;openPinnedView(' + i + ')">' +
17
17
  '<div style="display:flex;justify-content:space-between;align-items:center">' +
18
18
  '<strong style="font-size:var(--text-md)">' + escHtml(e.title) + '</strong>' +
19
19
  '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--red);border-color:var(--red)" data-pin-title="' + escHtml(e.title) + '" onclick="event.stopPropagation();removePinnedNote(this.dataset.pinTitle)">Unpin</button>' +
@@ -277,7 +277,7 @@ function renderPipelines(pipelines) {
277
277
  var allResources = _collectMonitoredResources(p);
278
278
  var resourcesHtml = _renderMonitoredResources(allResources, { compact: true });
279
279
 
280
- return '<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:8px;cursor:pointer" onclick="openPipelineDetail(\'' + escHtml(p.id) + '\')">' +
280
+ return '<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:8px;cursor:pointer" onclick="if(shouldIgnoreSelectionClick(event))return;openPipelineDetail(\'' + escHtml(p.id) + '\')">' +
281
281
  '<div style="display:flex;justify-content:space-between;align-items:center">' +
282
282
  '<strong style="font-size:13px">' + escHtml(p.title) + '</strong>' +
283
283
  '<div style="display:flex;align-items:center;gap:8px">' +
@@ -263,7 +263,7 @@ function renderPlans(plans) {
263
263
  const versionBadge = p.version ? ' <span style="font-size:9px;font-weight:700;padding:1px 5px;border-radius:3px;background:rgba(56,139,253,0.15);color:var(--blue);vertical-align:middle">v' + p.version + '</span>' : '';
264
264
  const statusColors = { 'completed': 'var(--green)', 'dispatched': 'var(--blue)', 'converting': 'var(--yellow)', 'paused': 'var(--muted)', 'awaiting-approval': 'var(--yellow)', 'approved': 'var(--green)', 'rejected': 'var(--red)', 'has-failures': 'var(--red)', 'revision-requested': 'var(--purple,#a855f7)', 'active': 'var(--muted)' };
265
265
  const cardClass = effectiveStatus === 'dispatched' || effectiveStatus === 'converting' ? 'working' : effectiveStatus === 'awaiting-approval' || effectiveStatus === 'paused' ? 'awaiting' : effectiveStatus;
266
- return '<div class="plan-card ' + cardClass + '" data-file="plans/' + escHtml(p.file) + '" style="cursor:pointer' + (isArchived ? ';opacity:0.7' : '') + '" onclick="planView(\'' + escHtml(p.file) + '\')">' +
266
+ return '<div class="plan-card ' + cardClass + '" data-file="plans/' + escHtml(p.file) + '" style="cursor:pointer' + (isArchived ? ';opacity:0.7' : '') + '" onclick="if(shouldIgnoreSelectionClick(event))return;planView(\'' + escHtml(p.file) + '\')">' +
267
267
  '<div class="plan-card-header">' +
268
268
  '<div><div class="plan-card-title">' + escHtml(p.summary || p.file) + versionBadge + '</div>' +
269
269
  '<div class="plan-card-meta">' +
@@ -323,7 +323,7 @@ function openArchivedPlansModal() {
323
323
  const html = plans.map(p => {
324
324
  const itemCount = p.itemCount || 0;
325
325
  const completed = p.completedAt ? p.completedAt.slice(0, 10) : '';
326
- return '<div class="plan-card" data-file="plans/' + escHtml(p.file) + '" style="cursor:pointer;opacity:0.8" onclick="planView(\'' + escHtml(p.file) + '\')">' +
326
+ return '<div class="plan-card" data-file="plans/' + escHtml(p.file) + '" style="cursor:pointer;opacity:0.8" onclick="if(shouldIgnoreSelectionClick(event))return;planView(\'' + escHtml(p.file) + '\')">' +
327
327
  '<div class="plan-card-header">' +
328
328
  '<div><div class="plan-card-title" style="font-size:13px">' + escHtml(p.summary || p.file) + '</div>' +
329
329
  '<div class="plan-card-meta">' +
@@ -203,7 +203,7 @@ function renderPrdProgress(prog) {
203
203
  ? '<span onclick="event.stopPropagation();prdItemReopen(\'' + escHtml(i.source || '') + '\',\'' + escHtml(i.id) + '\')" style="color:var(--blue);cursor:pointer;font-size:9px;padding:1px 5px;background:rgba(56,139,253,0.1);border:1px solid rgba(56,139,253,0.3);border-radius:3px" title="Re-open: set to updated so engine re-dispatches on existing branch">re-open</span>'
204
204
  : '';
205
205
 
206
- return '<div class="prd-item-row st-' + (i.status || 'missing') + '" style="flex-wrap:wrap;cursor:pointer" onclick="prdItemEdit(\'' + src + '\',\'' + iid + '\')">' +
206
+ return '<div class="prd-item-row st-' + (i.status || 'missing') + '" style="flex-wrap:wrap;cursor:pointer" onclick="if(shouldIgnoreSelectionClick(event))return;prdItemEdit(\'' + src + '\',\'' + iid + '\')">' +
207
207
  statusBadge(i.status, i.id) +
208
208
  '<span class="prd-item-id">' + escHtml(i.id) + '</span>' +
209
209
  '<span class="prd-item-name" title="' + escHtml(i.name) + '">' + escHtml(i.name) + '</span>' +
@@ -579,7 +579,7 @@ function openArchivedPrdModal() {
579
579
  const done = g.items.filter(it => it.status === 'done').length;
580
580
  const failed = g.items.filter(it => it.status === 'failed').length;
581
581
  const completed = g.completedAt ? new Date(g.completedAt).toLocaleDateString() : '';
582
- return '<div class="plan-card" style="cursor:pointer;margin-bottom:8px" onclick="showArchivedPrdDetail(\'' + escHtml(g.file) + '\')">' +
582
+ return '<div class="plan-card" style="cursor:pointer;margin-bottom:8px" onclick="if(shouldIgnoreSelectionClick(event))return;showArchivedPrdDetail(\'' + escHtml(g.file) + '\')">' +
583
583
  '<div class="plan-card-title" style="font-size:13px">' + escHtml(g.summary || g.file) + '</div>' +
584
584
  '<div class="plan-card-meta">' +
585
585
  (g._projects.length > 0 ? g._projects.map(function(p) { return '<span>' + escHtml(p) + '</span>'; }).join(' ') : '') +
@@ -250,7 +250,7 @@ function _renderScheduleCalendar(schedules) {
250
250
  var strikeStyle = s.enabled === false ? 'text-decoration:line-through;' : '';
251
251
  var timeLabel = String(sl.hour).padStart(2, '0') + ':' + String(sl.minute).padStart(2, '0');
252
252
  html += '<div class="sched-cal-slot" style="background:' + colors.bg + ';border-left-color:' + colors.border + ';color:' + colors.text + ';opacity:' + opacity + '" ' +
253
- 'onclick="openScheduleDetail(\'' + escHtml(s.id) + '\')" title="' + escHtml(s.title + ' — ' + timeLabel + ' — ' + (s.type || 'implement')) + '">' +
253
+ 'onclick="if(shouldIgnoreSelectionClick(event))return;openScheduleDetail(\'' + escHtml(s.id) + '\')" title="' + escHtml(s.title + ' — ' + timeLabel + ' — ' + (s.type || 'implement')) + '">' +
254
254
  '<span style="font-weight:600;' + strikeStyle + '">' + escHtml((s.title || s.id).slice(0, 25)) + '</span>' +
255
255
  '<span style="font-size:9px;opacity:0.7"> ' + timeLabel + '</span>' +
256
256
  '</div>';
@@ -292,7 +292,7 @@ function renderSchedules(schedules) {
292
292
  const lastRun = s._lastRun ? timeAgo(s._lastRun) : 'never';
293
293
  const typeBadge = '<span class="dispatch-type ' + escHtml(s.type || 'implement') + '">' + escHtml(s.type || 'implement') + '</span>';
294
294
  const humanCron = _cronToHuman(s.cron || '');
295
- html += '<tr style="cursor:pointer" onclick="openScheduleDetail(\'' + escHtml(s.id) + '\')">' +
295
+ html += '<tr style="cursor:pointer" onclick="if(shouldIgnoreSelectionClick(event))return;openScheduleDetail(\'' + escHtml(s.id) + '\')">' +
296
296
  '<td><span class="pr-id">' + escHtml(s.id || '') + '</span></td>' +
297
297
  '<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(s.title || '') + '">' + escHtml(s.title || '') + '</td>' +
298
298
  '<td><span title="' + escHtml(s.cron || '') + '" style="font-size:11px;color:var(--blue)">' + escHtml(humanCron) + '</span></td>' +
@@ -89,7 +89,7 @@ function renderWatches(watchesData) {
89
89
  var lastTriggered = w.last_triggered ? timeAgo(w.last_triggered) : 'never';
90
90
  var triggerInfo = (w.triggerCount || 0) + (w.stopAfter > 0 ? '/' + w.stopAfter : '');
91
91
 
92
- html += '<tr style="cursor:pointer" onclick="openWatchDetail(\'' + escHtml(w.id) + '\')">' +
92
+ html += '<tr style="cursor:pointer" onclick="if(shouldIgnoreSelectionClick(event))return;openWatchDetail(\'' + escHtml(w.id) + '\')">' +
93
93
  '<td><span class="pr-id">' + escHtml(w.id) + '</span></td>' +
94
94
  '<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(w.description || w.target) + '">' + escHtml(w.target) + '</td>' +
95
95
  '<td><span class="dispatch-type explore">' + escHtml(targetLabel) + '</span></td>' +
@@ -40,7 +40,7 @@ function wiRow(item) {
40
40
  : (item.branchStrategy === 'shared-branch' && item.status === 'done')
41
41
  ? '<span style="font-size:9px;color:var(--muted)" title="Part of shared branch — aggregate PR created at verify stage">shared branch</span>'
42
42
  : '<span style="color:var(--muted)">—</span>';
43
- return '<tr style="cursor:pointer" onclick="if(window.getSelection().toString().length>0)return;openWorkItemDetail(\'' + escHtml(item.id) + '\')">' +
43
+ return '<tr style="cursor:pointer" onclick="if(shouldIgnoreSelectionClick(event))return;openWorkItemDetail(\'' + escHtml(item.id) + '\')">' +
44
44
  '<td><span class="pr-id">' + escHtml(item.id || '') + '</span></td>' +
45
45
  '<td style="max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml((item.title || '').slice(0, 200)) + '">' + escHtml(item.title || '') + '</td>' +
46
46
  '<td><span style="font-size:10px;color:var(--muted)">' + escHtml(item._source || '') + '</span>' +
@@ -68,8 +68,8 @@ async function openSettings() {
68
68
  '</div>' +
69
69
  settingsToggle('GitHub Polling', 'set-ghPollEnabled', e.ghPollEnabled !== false, 'Keeps GitHub PR build results, votes, and comments fresh each tick (reconciliation always runs regardless)') +
70
70
  '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px">' +
71
- settingsField('Status Poll Frequency', 'set-prPollStatusEvery', e.prPollStatusEvery ?? e.adoPollStatusEvery ?? 12, 'ticks', 'Poll PR build/review/merge status every N ticks for both ADO and GitHub (~12 min at default tick rate)') +
72
- settingsField('Comments Poll Frequency', 'set-prPollCommentsEvery', e.prPollCommentsEvery ?? e.adoPollCommentsEvery ?? 12, 'ticks', 'Poll PR human comments every N ticks for both ADO and GitHub (~12 min at default tick rate)') +
71
+ settingsField('PR Status Poll Frequency', 'set-prPollStatusEvery', e.prPollStatusEvery ?? e.adoPollStatusEvery ?? 12, 'ticks', 'Poll PR build/review/merge status every N ticks for both ADO and GitHub (~12 min at default tick rate)') +
72
+ settingsField('PR Comments Poll Frequency', 'set-prPollCommentsEvery', e.prPollCommentsEvery ?? e.adoPollCommentsEvery ?? 12, 'ticks', 'Poll PR human comments every N ticks for both ADO and GitHub (~12 min at default tick rate)') +
73
73
  '</div>' +
74
74
  '</div>' +
75
75
 
@@ -129,6 +129,26 @@ function statusColor(s) {
129
129
  return s === 'working' ? 'working' : s === 'done' ? 'done' : s === 'error' ? 'error' : '';
130
130
  }
131
131
 
132
+ var SELECTION_CLICK_GUARD_MS = 300;
133
+ var _lastTextSelectionAt = 0;
134
+
135
+ function _selectionHasText() {
136
+ var sel = window.getSelection ? window.getSelection() : null;
137
+ return !!(sel && !sel.isCollapsed && sel.toString().trim());
138
+ }
139
+
140
+ function _markRecentTextSelection() {
141
+ if (_selectionHasText()) _lastTextSelectionAt = Date.now();
142
+ }
143
+
144
+ document.addEventListener('selectionchange', _markRecentTextSelection);
145
+ document.addEventListener('mouseup', _markRecentTextSelection);
146
+
147
+ function shouldIgnoreSelectionClick() {
148
+ if (_selectionHasText()) return true;
149
+ return (Date.now() - _lastTextSelectionAt) < SELECTION_CLICK_GUARD_MS;
150
+ }
151
+
132
152
  function llmCopyBtn() {
133
153
  return '<button class="llm-copy-btn" onclick="event.stopPropagation();copyLlmText(this)" title="Copy">&#x2398;</button>';
134
154
  }
@@ -462,4 +482,4 @@ async function submitBugReport() {
462
482
  }
463
483
  }
464
484
 
465
- window.MinionsUtils = { wakeEngine, escHtml, renderMd, normalizePlanFile, timeAgo, statusColor, llmCopyBtn, copyLlmText, openBugReport, submitBugReport };
485
+ window.MinionsUtils = { wakeEngine, escHtml, renderMd, normalizePlanFile, timeAgo, statusColor, shouldIgnoreSelectionClick, llmCopyBtn, copyLlmText, openBugReport, submitBugReport };
package/engine/ado.js CHANGED
@@ -465,6 +465,11 @@ async function pollPrStatus(config) {
465
465
  } catch (e) { log('warn', `ADO build query for ${pr.id}: ${e.message}`); }
466
466
  }
467
467
 
468
+ // Record actual poll time — makes lastBuildCheck reflect when the engine last
469
+ // talked to ADO, not when the agent was dispatched. Issue #1233.
470
+ pr.lastBuildCheck = ts();
471
+ updated = true;
472
+
468
473
  if (pr.buildStatus !== buildStatus) {
469
474
  log('info', `PR ${pr.id} build: ${pr.buildStatus || 'none'} → ${buildStatus}${buildFailReason ? ' (' + buildFailReason + ')' : ''}`);
470
475
  pr.buildStatus = buildStatus;
@@ -475,9 +480,17 @@ async function pollPrStatus(config) {
475
480
  if (buildStatus === 'failing') delete pr._autoCompleted;
476
481
  if (buildStatus !== 'failing') {
477
482
  delete pr._buildFailNotified;
478
- delete pr.buildErrorLog;
479
- // Reset build fix retry counter on recovery allows fresh auto-fix cycles if build breaks again
480
- if (pr.buildFixAttempts) { delete pr.buildFixAttempts; delete pr.buildFixEscalated; }
483
+ // Preserve buildErrorLog + buildFixAttempts through transient 'none'/'running'
484
+ // transitions only clear on confirmed 'passing' recovery. Issue #1232: 'none'
485
+ // can also occur when ADO recomputes the merge commit after a target-branch
486
+ // update but no new builds have been triggered yet (filter by sourceVersion
487
+ // returns []), which previously wiped the last known error log and caused
488
+ // fix agents to be dispatched blind.
489
+ if (buildStatus === 'passing') {
490
+ delete pr.buildErrorLog;
491
+ // Reset build fix retry counter on recovery — allows fresh auto-fix cycles if build breaks again
492
+ if (pr.buildFixAttempts) { delete pr.buildFixAttempts; delete pr.buildFixEscalated; }
493
+ }
481
494
  }
482
495
  updated = true;
483
496
 
package/engine/github.js CHANGED
@@ -422,6 +422,10 @@ async function pollPrStatus(config) {
422
422
  if (prData.state === 'open' && prData.head?.sha) {
423
423
  const checksData = await ghApi(`/commits/${prData.head.sha}/check-runs`, slug);
424
424
  if (checksData && checksData.check_runs) {
425
+ // Record actual poll time — makes lastBuildCheck reflect when the engine last
426
+ // talked to GitHub, not when the agent was dispatched. Issue #1233.
427
+ pr.lastBuildCheck = ts();
428
+ updated = true;
425
429
  const runs = checksData.check_runs;
426
430
  let buildStatus = 'none';
427
431
  let buildFailReason = '';
@@ -452,9 +456,15 @@ async function pollPrStatus(config) {
452
456
  if (buildStatus === 'failing') delete pr._autoCompleted; // allow re-merge after fix
453
457
  if (buildStatus !== 'failing') {
454
458
  delete pr._buildFailNotified;
455
- delete pr.buildErrorLog;
456
- // Reset build fix retry counter on recovery allows fresh auto-fix cycles if build breaks again
457
- if (pr.buildFixAttempts) { delete pr.buildFixAttempts; delete pr.buildFixEscalated; }
459
+ // Preserve buildErrorLog + buildFixAttempts through transient 'none'/'running'
460
+ // transitions only clear on confirmed 'passing' recovery. Issue #1232:
461
+ // clearing on every non-failing transition blinded the next fix agent
462
+ // while a queued build was still running.
463
+ if (buildStatus === 'passing') {
464
+ delete pr.buildErrorLog;
465
+ // Reset build fix retry counter on recovery — allows fresh auto-fix cycles if build breaks again
466
+ if (pr.buildFixAttempts) { delete pr.buildFixAttempts; delete pr.buildFixEscalated; }
467
+ }
458
468
  }
459
469
  updated = true;
460
470
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1105",
3
+ "version": "0.1.1107",
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"