@yemi33/minions 0.1.153 → 0.1.154
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 +4 -1
- package/dashboard/js/command-center.js +3 -5
- package/dashboard/js/refresh.js +44 -0
- package/dashboard/js/render-work-items.js +42 -8
- package/dashboard/styles.css +4 -0
- package/engine/lifecycle.js +60 -47
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.154 (2026-04-02)
|
|
4
4
|
|
|
5
5
|
### Engine
|
|
6
6
|
- engine.js
|
|
@@ -11,12 +11,15 @@
|
|
|
11
11
|
### Dashboard
|
|
12
12
|
- dashboard.html
|
|
13
13
|
- dashboard.js
|
|
14
|
+
- dashboard/js/command-center.js
|
|
14
15
|
- dashboard/js/modal-qa.js
|
|
15
16
|
- dashboard/js/modal.js
|
|
17
|
+
- dashboard/js/refresh.js
|
|
16
18
|
- dashboard/js/render-inbox.js
|
|
17
19
|
- dashboard/js/render-kb.js
|
|
18
20
|
- dashboard/js/render-plans.js
|
|
19
21
|
- dashboard/js/render-prd.js
|
|
22
|
+
- dashboard/js/render-work-items.js
|
|
20
23
|
- dashboard/styles.css
|
|
21
24
|
|
|
22
25
|
### Documentation
|
|
@@ -88,7 +88,7 @@ function ccAddMessage(role, html, skipSave) {
|
|
|
88
88
|
const isUser = role === 'user';
|
|
89
89
|
const div = document.createElement('div');
|
|
90
90
|
const isAssistant = !isUser;
|
|
91
|
-
div.className = isAssistant ? 'cc-msg-assistant' : '';
|
|
91
|
+
div.className = isAssistant ? 'cc-msg-assistant md-content' : '';
|
|
92
92
|
div.style.cssText = 'padding:8px 12px;border-radius:8px;font-size:12px;line-height:1.6;max-width:95%;' +
|
|
93
93
|
(isUser ? 'background:var(--blue);color:#fff;align-self:flex-end' : 'background:var(--surface2);color:var(--text);align-self:flex-start;border:1px solid var(--border);position:relative');
|
|
94
94
|
div.innerHTML = (isAssistant && !html.includes('color:var(--red)') && !html.includes('cc-queued-pill') ? llmCopyBtn() : '') + html;
|
|
@@ -205,11 +205,9 @@ async function _ccDoSend(message, skipUserMsg) {
|
|
|
205
205
|
ccUpdateSessionIndicator();
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
// Render markdown
|
|
208
|
+
// Render markdown response
|
|
209
209
|
const ccElapsed = Math.round((Date.now() - ccStartTime) / 1000);
|
|
210
|
-
const rendered = (data.text || '')
|
|
211
|
-
.replace(/`([^`]+)`/g, '<code style="background:var(--surface);padding:1px 4px;border-radius:3px;font-size:11px">$1</code>')
|
|
212
|
-
.replace(/\n/g, '<br>');
|
|
210
|
+
const rendered = renderMd(data.text || '');
|
|
213
211
|
ccAddMessage('assistant', rendered + '<div style="font-size:9px;color:var(--muted);margin-top:6px;display:flex;justify-content:flex-end;padding-right:30px">' + ccElapsed + 's</div>');
|
|
214
212
|
|
|
215
213
|
// Execute actions
|
package/dashboard/js/refresh.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// Sidebar activity indicators — detect changes between refreshes
|
|
4
4
|
let _prevCounts = {};
|
|
5
|
+
let _prevEngineAlert = false;
|
|
5
6
|
function _detectPageChanges(data) {
|
|
6
7
|
const counts = {
|
|
7
8
|
completions: (data.dispatch?.completed || []).length,
|
|
@@ -22,9 +23,52 @@ function _detectPageChanges(data) {
|
|
|
22
23
|
if (counts.meetingRounds > _prevCounts.meetingRounds) changes.meetings = true;
|
|
23
24
|
}
|
|
24
25
|
_prevCounts = counts;
|
|
26
|
+
|
|
27
|
+
// Engine page — only badge for genuine problems, not routine activity
|
|
28
|
+
const engineAlert = _isEngineAlertWorthy(data);
|
|
29
|
+
if (engineAlert && !_prevEngineAlert) changes.engine = true;
|
|
30
|
+
// Clear the engine badge when alert condition resolves
|
|
31
|
+
if (!engineAlert && _prevEngineAlert) {
|
|
32
|
+
const engineLink = document.querySelector('.sidebar-link[data-page="engine"]');
|
|
33
|
+
if (engineLink) clearNotifBadge(engineLink);
|
|
34
|
+
}
|
|
35
|
+
_prevEngineAlert = engineAlert;
|
|
36
|
+
|
|
25
37
|
return changes;
|
|
26
38
|
}
|
|
27
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Determine if the engine state warrants a notification dot.
|
|
42
|
+
* Returns true only for genuine problems:
|
|
43
|
+
* - Engine stopped, stale, or in error state
|
|
44
|
+
* - 3+ failed work items in the last hour
|
|
45
|
+
* - Agent timeout/crash detected (error results in recent completions)
|
|
46
|
+
*/
|
|
47
|
+
function _isEngineAlertWorthy(data) {
|
|
48
|
+
// 1. Engine not running (stopped, stale, or error)
|
|
49
|
+
const engineState = data.engine?.state || 'stopped';
|
|
50
|
+
if (engineState === 'stopped' || engineState === 'error') return true;
|
|
51
|
+
// Stale heartbeat (>2 min old while claiming running)
|
|
52
|
+
if (engineState === 'running' && data.engine?.heartbeat) {
|
|
53
|
+
if (Date.now() - data.engine.heartbeat > 120000) return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. 3+ failed work items in the last hour
|
|
57
|
+
const oneHourAgo = Date.now() - 3600000;
|
|
58
|
+
const recentFailures = (data.workItems || []).filter(w =>
|
|
59
|
+
w.status === 'failed' && w.updated_at && new Date(w.updated_at).getTime() > oneHourAgo
|
|
60
|
+
);
|
|
61
|
+
if (recentFailures.length >= 3) return true;
|
|
62
|
+
|
|
63
|
+
// 3. Agent timeout/crash — 3+ error results in recent completed dispatches
|
|
64
|
+
const recentErrors = (data.dispatch?.completed || []).filter(d =>
|
|
65
|
+
d.result === 'error' && d.completed_at && new Date(d.completed_at).getTime() > oneHourAgo
|
|
66
|
+
);
|
|
67
|
+
if (recentErrors.length >= 3) return true;
|
|
68
|
+
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
28
72
|
function _processStatusUpdate(data) {
|
|
29
73
|
// Detect fresh install — clear stale browser state if install ID changed
|
|
30
74
|
if (data.installId) {
|
|
@@ -4,6 +4,30 @@ let allWorkItems = [];
|
|
|
4
4
|
let wiPage = 0;
|
|
5
5
|
const WI_PER_PAGE = 20;
|
|
6
6
|
|
|
7
|
+
// Track retry state per work item so loading/success/error survives re-renders
|
|
8
|
+
const _wiRetryState = {}; // { [id]: { status: 'pending'|'done'|'error', message?, until? } }
|
|
9
|
+
function setWiRetryState(id, state) { _wiRetryState[id] = state; }
|
|
10
|
+
function getWiRetryState(id) {
|
|
11
|
+
const s = _wiRetryState[id];
|
|
12
|
+
if (!s) return null;
|
|
13
|
+
if (s.until && Date.now() > s.until) { delete _wiRetryState[id]; return null; }
|
|
14
|
+
return s;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function wiRetryBtn(item) {
|
|
18
|
+
const rs = getWiRetryState(item.id);
|
|
19
|
+
if (rs && rs.status === 'pending') {
|
|
20
|
+
return '<span style="font-size:9px;padding:1px 6px;color:var(--yellow);border:1px solid rgba(210,153,34,0.35);background:rgba(210,153,34,0.1);border-radius:3px;cursor:wait;margin-left:4px">Retrying\u2026</span>';
|
|
21
|
+
}
|
|
22
|
+
if (rs && rs.status === 'done') {
|
|
23
|
+
return '<span style="font-size:9px;padding:1px 6px;color:var(--green);border:1px solid rgba(63,185,80,0.35);background:rgba(63,185,80,0.1);border-radius:3px;margin-left:4px">Requeued</span>';
|
|
24
|
+
}
|
|
25
|
+
if (rs && rs.status === 'error') {
|
|
26
|
+
return '<span style="font-size:9px;padding:1px 6px;color:var(--red);border:1px solid rgba(248,81,73,0.35);background:rgba(248,81,73,0.1);border-radius:3px;margin-left:4px;cursor:pointer" title="' + escHtml(rs.message || 'Retry failed') + ' — click to try again" onclick="event.stopPropagation();retryWorkItem(\'' + escHtml(item.id) + '\',\'' + escHtml(item._source || '') + '\')">Retry failed</span>';
|
|
27
|
+
}
|
|
28
|
+
return '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--yellow);border-color:var(--yellow);margin-left:4px" onclick="event.stopPropagation();retryWorkItem(\'' + escHtml(item.id) + '\',\'' + escHtml(item._source || '') + '\')">Retry</button>';
|
|
29
|
+
}
|
|
30
|
+
|
|
7
31
|
function wiRow(item) {
|
|
8
32
|
const statusBadge = (s) => {
|
|
9
33
|
const cls = s === 'failed' ? 'rejected' : s === 'needs-human-review' ? 'needs-review' : s === 'dispatched' ? 'building' : s === 'pending' || s === 'queued' ? 'active' : s === 'done' ? 'approved' : 'draft';
|
|
@@ -23,7 +47,7 @@ function wiRow(item) {
|
|
|
23
47
|
'<td>' + priBadge(item.priority) + '</td>' +
|
|
24
48
|
'<td>' + statusBadge(item.status || 'pending') +
|
|
25
49
|
(item._pendingReason ? ' <span style="font-size:9px;color:var(--muted);margin-left:4px" title="Pending reason: ' + escHtml(item._pendingReason) + '">' + escHtml(item._pendingReason.replace(/_/g, ' ')) + '</span>' : '') +
|
|
26
|
-
(item.status === 'failed' ? '
|
|
50
|
+
(item.status === 'failed' ? ' ' + wiRetryBtn(item) : '') +
|
|
27
51
|
'</td>' +
|
|
28
52
|
'<td>' +
|
|
29
53
|
(item.completedAgents && item.completedAgents.length > 0
|
|
@@ -92,7 +116,7 @@ function renderWorkItems(items) {
|
|
|
92
116
|
function editWorkItem(id, source) {
|
|
93
117
|
const item = allWorkItems.find(i => i.id === id);
|
|
94
118
|
if (!item) return;
|
|
95
|
-
const types = ['implement', 'fix', 'review', 'plan', 'verify', 'investigate', 'refactor', 'test', 'docs'];
|
|
119
|
+
const types = ['implement', 'fix', 'review', 'plan', 'verify', 'evaluate', 'decompose', 'meeting', 'investigate', 'refactor', 'test', 'explore', 'ask', 'docs'];
|
|
96
120
|
const priorities = ['critical', 'high', 'medium', 'low'];
|
|
97
121
|
const agentOpts = (cmdAgents || []).map(a => '<option value="' + escHtml(a.id) + '"' + (item.agent === a.id ? ' selected' : '') + '>' + escHtml(a.name) + '</option>').join('');
|
|
98
122
|
const typeOpts = types.map(t => '<option value="' + t + '"' + ((item.type || 'implement') === t ? ' selected' : '') + '>' + t + '</option>').join('');
|
|
@@ -211,23 +235,33 @@ async function toggleWorkItemArchive() {
|
|
|
211
235
|
} catch (e) { el.innerHTML = '<p class="empty">Failed to load archive.</p>'; }
|
|
212
236
|
}
|
|
213
237
|
|
|
214
|
-
async function retryWorkItem(id, source
|
|
215
|
-
|
|
238
|
+
async function retryWorkItem(id, source) {
|
|
239
|
+
// Prevent double-click: if already retrying, ignore
|
|
240
|
+
const existing = getWiRetryState(id);
|
|
241
|
+
if (existing && existing.status === 'pending') return;
|
|
242
|
+
|
|
243
|
+
setWiRetryState(id, { status: 'pending' });
|
|
244
|
+
renderWorkItems(allWorkItems);
|
|
216
245
|
try {
|
|
217
246
|
const res = await fetch('/api/work-items/retry', {
|
|
218
247
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
219
248
|
body: JSON.stringify({ id, source: source || undefined })
|
|
220
249
|
});
|
|
221
250
|
if (res.ok) {
|
|
251
|
+
setWiRetryState(id, { status: 'done', until: Date.now() + 8000 });
|
|
222
252
|
showToast('cmd-toast', 'Work item ' + id + ' reset to pending', true);
|
|
223
253
|
wakeEngine();
|
|
224
254
|
refresh();
|
|
225
255
|
} else {
|
|
226
|
-
if (btn) { btn.textContent = btn.dataset.origText || 'Retry'; btn.style.pointerEvents = ''; btn.style.opacity = ''; }
|
|
227
256
|
const d = await res.json().catch(() => ({}));
|
|
228
|
-
|
|
257
|
+
const msg = d.error || 'unknown';
|
|
258
|
+
setWiRetryState(id, { status: 'error', message: msg, until: Date.now() + 10000 });
|
|
259
|
+
renderWorkItems(allWorkItems);
|
|
229
260
|
}
|
|
230
|
-
} catch (e) {
|
|
261
|
+
} catch (e) {
|
|
262
|
+
setWiRetryState(id, { status: 'error', message: e.message, until: Date.now() + 10000 });
|
|
263
|
+
renderWorkItems(allWorkItems);
|
|
264
|
+
}
|
|
231
265
|
}
|
|
232
266
|
|
|
233
267
|
function wiPrev() { if (wiPage > 0) { wiPage--; renderWorkItems(allWorkItems); } }
|
|
@@ -282,7 +316,7 @@ async function submitFeedback(id, source) {
|
|
|
282
316
|
}
|
|
283
317
|
|
|
284
318
|
function openCreateWorkItemModal() {
|
|
285
|
-
const typeOpts = ['implement', 'fix', 'explore', 'test', 'review', 'ask', 'plan'].map(t =>
|
|
319
|
+
const typeOpts = ['implement', 'fix', 'explore', 'test', 'review', 'ask', 'plan', 'verify', 'evaluate', 'decompose', 'meeting'].map(t =>
|
|
286
320
|
'<option value="' + t + '"' + (t === 'implement' ? ' selected' : '') + '>' + t + '</option>'
|
|
287
321
|
).join('');
|
|
288
322
|
const priOpts = ['high', 'medium', 'low'].map(p =>
|
package/dashboard/styles.css
CHANGED
|
@@ -505,6 +505,10 @@
|
|
|
505
505
|
.dispatch-type.plan { background: rgba(168,85,247,0.15); color: #a855f7; }
|
|
506
506
|
.dispatch-type.plan-to-prd { background: rgba(168,85,247,0.1); color: #a855f7; }
|
|
507
507
|
.dispatch-type.ask { background: rgba(63,185,80,0.15); color: var(--green); }
|
|
508
|
+
.dispatch-type.evaluate { background: rgba(248,81,73,0.15); color: var(--red); }
|
|
509
|
+
.dispatch-type.verify { background: rgba(63,185,80,0.2); color: var(--green); }
|
|
510
|
+
.dispatch-type.meeting { background: rgba(88,166,255,0.2); color: var(--blue); }
|
|
511
|
+
.dispatch-type.decompose { background: rgba(188,140,255,0.2); color: var(--purple); }
|
|
508
512
|
.dispatch-type.manual { background: rgba(139,148,158,0.15); color: var(--muted); }
|
|
509
513
|
.dispatch-agent { font-weight: 600; color: var(--text); }
|
|
510
514
|
.dispatch-task { flex: 1; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
package/engine/lifecycle.js
CHANGED
|
@@ -603,7 +603,14 @@ function syncPrsFromOutput(output, agentId, meta, config) {
|
|
|
603
603
|
dirtyTargets.set(targetName, { prs: safeJson(prPath) || [], prPath });
|
|
604
604
|
}
|
|
605
605
|
const entry = dirtyTargets.get(targetName);
|
|
606
|
-
|
|
606
|
+
const existing = entry.prs.find(p => p.id === fullId || String(p.id) === String(prId));
|
|
607
|
+
if (existing) {
|
|
608
|
+
// Backfill prdItems if the entry was added by the poller before syncPrsFromOutput ran
|
|
609
|
+
if (meta?.item?.id && !existing.prdItems?.includes(meta.item.id)) {
|
|
610
|
+
existing.prdItems = [...(existing.prdItems || []), meta.item.id];
|
|
611
|
+
}
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
607
614
|
|
|
608
615
|
let title = meta?.item?.title || '';
|
|
609
616
|
const titleMatch = output.match(new RegExp(`${prId}[^\\n]*?[—–-]\\s*([^\\n]+)`, 'i'));
|
|
@@ -637,10 +644,26 @@ function syncPrsFromOutput(output, agentId, meta, config) {
|
|
|
637
644
|
|
|
638
645
|
// ─── Post-Completion Hooks ──────────────────────────────────────────────────
|
|
639
646
|
|
|
647
|
+
/**
|
|
648
|
+
* Resolve which project's pull-requests.json contains a given PR ID.
|
|
649
|
+
* Returns the project object, or null if not found in any project file.
|
|
650
|
+
*/
|
|
651
|
+
function resolveProjectForPr(prId) {
|
|
652
|
+
const config = getConfig();
|
|
653
|
+
for (const p of shared.getProjects(config)) {
|
|
654
|
+
const prs = safeJson(projectPrPath(p)) || [];
|
|
655
|
+
if (prs.some(pr => pr.id === prId)) return p;
|
|
656
|
+
}
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
|
|
640
660
|
function updatePrAfterReview(agentId, pr, project) {
|
|
641
661
|
|
|
642
662
|
if (!pr?.id) return;
|
|
643
|
-
|
|
663
|
+
// Resolve actual project if not provided — avoids writing merged array to wrong path
|
|
664
|
+
const resolvedProject = project || resolveProjectForPr(pr.id);
|
|
665
|
+
if (!resolvedProject) { log('warn', `updatePrAfterReview: cannot resolve project for ${pr.id}`); return; }
|
|
666
|
+
const prs = getPrs(resolvedProject);
|
|
644
667
|
const target = prs.find(p => p.id === pr.id);
|
|
645
668
|
if (!target) return;
|
|
646
669
|
|
|
@@ -674,7 +697,7 @@ function updatePrAfterReview(agentId, pr, project) {
|
|
|
674
697
|
shared.safeWrite(metricsPath, metrics);
|
|
675
698
|
}
|
|
676
699
|
|
|
677
|
-
shared.safeWrite(
|
|
700
|
+
shared.safeWrite(shared.projectPrPath(resolvedProject), prs);
|
|
678
701
|
log('info', `Updated ${pr.id} → minions review: ${target.reviewStatus} by ${reviewerName}`);
|
|
679
702
|
createReviewFeedbackForAuthor(agentId, { ...pr, ...target }, config);
|
|
680
703
|
}
|
|
@@ -682,7 +705,10 @@ function updatePrAfterReview(agentId, pr, project) {
|
|
|
682
705
|
function updatePrAfterFix(pr, project, source) {
|
|
683
706
|
|
|
684
707
|
if (!pr?.id) return;
|
|
685
|
-
|
|
708
|
+
// Resolve actual project if not provided — avoids writing merged array to wrong path
|
|
709
|
+
const resolvedProject = project || resolveProjectForPr(pr.id);
|
|
710
|
+
if (!resolvedProject) { log('warn', `updatePrAfterFix: cannot resolve project for ${pr.id}`); return; }
|
|
711
|
+
const prs = getPrs(resolvedProject);
|
|
686
712
|
const target = prs.find(p => p.id === pr.id);
|
|
687
713
|
if (!target) return;
|
|
688
714
|
|
|
@@ -697,7 +723,7 @@ function updatePrAfterFix(pr, project, source) {
|
|
|
697
723
|
log('info', `Updated ${pr.id} → reviewStatus: waiting (fix pushed)`);
|
|
698
724
|
}
|
|
699
725
|
|
|
700
|
-
shared.safeWrite(
|
|
726
|
+
shared.safeWrite(shared.projectPrPath(resolvedProject), prs);
|
|
701
727
|
}
|
|
702
728
|
|
|
703
729
|
// ─── Post-Merge / Post-Close Hooks ───────────────────────────────────────────
|
|
@@ -1323,51 +1349,37 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1323
1349
|
}
|
|
1324
1350
|
|
|
1325
1351
|
if (!isSuccess && meta?.item?.id) {
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
const wiPath = meta.source === 'central-work-item' || meta.source === 'central-work-item-fanout'
|
|
1330
|
-
? path.join(MINIONS_DIR, 'work-items.json')
|
|
1331
|
-
: meta.project?.name ? path.join(MINIONS_DIR, 'projects', meta.project.name, 'work-items.json') : null;
|
|
1332
|
-
if (wiPath) {
|
|
1333
|
-
const items = safeJson(wiPath) || [];
|
|
1334
|
-
const wi = items.find(i => i.id === meta.item.id);
|
|
1335
|
-
if (wi) retries = (wi._retryCount || 0); // Use fresh value from file
|
|
1336
|
-
}
|
|
1337
|
-
} catch { /* optional */ }
|
|
1338
|
-
|
|
1339
|
-
if (retries < 3) {
|
|
1340
|
-
log('info', `Agent failed for ${meta.item.id} — auto-retry ${retries + 1}/3`);
|
|
1341
|
-
updateWorkItemStatus(meta, 'pending', '');
|
|
1352
|
+
const wiPath = resolveWiPath(meta);
|
|
1353
|
+
if (wiPath) {
|
|
1354
|
+
let finalStatus = null;
|
|
1342
1355
|
try {
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
: meta.project?.name ? path.join(MINIONS_DIR, 'projects', meta.project.name, 'work-items.json') : null;
|
|
1346
|
-
if (wiPath) {
|
|
1347
|
-
const items = safeJson(wiPath) || [];
|
|
1356
|
+
mutateJsonFileLocked(wiPath, (items) => {
|
|
1357
|
+
if (!Array.isArray(items)) return items;
|
|
1348
1358
|
const wi = items.find(i => i.id === meta.item.id);
|
|
1349
|
-
if (wi)
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1359
|
+
if (!wi) return items;
|
|
1360
|
+
|
|
1361
|
+
const retries = wi._retryCount || 0;
|
|
1362
|
+
if (retries < 3) {
|
|
1363
|
+
log('info', `Agent failed for ${meta.item.id} — auto-retry ${retries + 1}/3`);
|
|
1364
|
+
wi._retryCount = retries + 1;
|
|
1365
|
+
wi.status = 'pending';
|
|
1366
|
+
delete wi.dispatched_at;
|
|
1367
|
+
delete wi.dispatched_to;
|
|
1368
|
+
finalStatus = 'pending';
|
|
1369
|
+
} else {
|
|
1370
|
+
wi.status = 'failed';
|
|
1371
|
+
wi.failReason = 'Agent failed (3 retries exhausted)';
|
|
1372
|
+
wi.failedAt = ts();
|
|
1373
|
+
finalStatus = 'failed';
|
|
1353
1374
|
}
|
|
1354
|
-
|
|
1375
|
+
if (type === 'decompose') delete wi._decomposing;
|
|
1376
|
+
return items;
|
|
1377
|
+
});
|
|
1355
1378
|
} catch (err) { log('warn', `Retry update: ${err.message}`); }
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
if (type === 'decompose') {
|
|
1361
|
-
try {
|
|
1362
|
-
const wiPath = meta.source === 'central-work-item' || meta.source === 'central-work-item-fanout'
|
|
1363
|
-
? path.join(MINIONS_DIR, 'work-items.json')
|
|
1364
|
-
: meta.project?.name ? path.join(MINIONS_DIR, 'projects', meta.project.name, 'work-items.json') : null;
|
|
1365
|
-
if (wiPath) {
|
|
1366
|
-
const items = safeJson(wiPath) || [];
|
|
1367
|
-
const wi = items.find(i => i.id === meta.item.id);
|
|
1368
|
-
if (wi) { delete wi._decomposing; shared.safeWrite(wiPath, items); }
|
|
1369
|
-
}
|
|
1370
|
-
} catch (err) { log('warn', `Decompose cleanup: ${err.message}`); }
|
|
1379
|
+
// Sync status to PRD outside the work-items lock
|
|
1380
|
+
if (finalStatus) {
|
|
1381
|
+
syncPrdItemStatus(meta.item.id, finalStatus, meta.item?.sourcePlan);
|
|
1382
|
+
}
|
|
1371
1383
|
}
|
|
1372
1384
|
}
|
|
1373
1385
|
// Meeting post-completion: collect findings/debate/conclusion
|
|
@@ -1420,7 +1432,7 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1420
1432
|
}
|
|
1421
1433
|
|
|
1422
1434
|
// Detect implement tasks that completed without creating a PR
|
|
1423
|
-
if (isSuccess && (type === 'implement' || type === 'implement:large' || type === 'fix') && prsCreatedCount === 0 && meta?.item?.id) {
|
|
1435
|
+
if (isSuccess && (type === 'implement' || type === 'implement:large' || type === 'fix') && prsCreatedCount === 0 && meta?.item?.id && !meta?.item?.skipPr) {
|
|
1424
1436
|
// Check if a PR already exists linked to this work item (from a previous attempt)
|
|
1425
1437
|
const projects = shared.getProjects(config);
|
|
1426
1438
|
const existingPrFound = Object.values(getPrLinks()).includes(meta.item.id);
|
|
@@ -1513,6 +1525,7 @@ module.exports = {
|
|
|
1513
1525
|
updateWorkItemStatus,
|
|
1514
1526
|
syncPrdItemStatus,
|
|
1515
1527
|
syncPrsFromOutput,
|
|
1528
|
+
resolveProjectForPr,
|
|
1516
1529
|
updatePrAfterReview,
|
|
1517
1530
|
updatePrAfterFix,
|
|
1518
1531
|
handlePostMerge,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.154",
|
|
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"
|