@yemi33/minions 0.1.78 → 0.1.80
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 +15 -0
- package/dashboard/js/modal-qa.js +14 -0
- package/dashboard/js/render-plans.js +20 -13
- package/dashboard/js/render-prd.js +10 -9
- package/dashboard.js +84 -3
- package/engine/queries.js +16 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.80 (2026-03-31)
|
|
4
|
+
|
|
5
|
+
### Engine
|
|
6
|
+
- engine/queries.js
|
|
7
|
+
|
|
8
|
+
### Dashboard
|
|
9
|
+
- dashboard.js
|
|
10
|
+
- dashboard/js/modal-qa.js
|
|
11
|
+
- dashboard/js/render-plans.js
|
|
12
|
+
|
|
13
|
+
## 0.1.79 (2026-03-31)
|
|
14
|
+
|
|
15
|
+
### Dashboard
|
|
16
|
+
- dashboard/js/render-prd.js
|
|
17
|
+
|
|
3
18
|
## 0.1.78 (2026-03-31)
|
|
4
19
|
|
|
5
20
|
### Engine
|
package/dashboard/js/modal-qa.js
CHANGED
|
@@ -218,6 +218,20 @@ async function _processQaMessage(message, selection) {
|
|
|
218
218
|
document.getElementById('modal-body').textContent = display;
|
|
219
219
|
_modalDocContext.content = display;
|
|
220
220
|
}
|
|
221
|
+
|
|
222
|
+
// If editing paused an active PRD, show re-execute actions
|
|
223
|
+
if (data.pausedPrd && capturedFilePath) {
|
|
224
|
+
const planFile = capturedFilePath.replace(/^plans\//, '');
|
|
225
|
+
const esc = planFile.replace(/'/g, "\\'");
|
|
226
|
+
const actionDiv = document.createElement('div');
|
|
227
|
+
actionDiv.style.cssText = 'margin:8px 0;padding:8px 12px;background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.3);border-radius:6px;display:flex;flex-wrap:wrap;align-items:center;gap:8px';
|
|
228
|
+
actionDiv.innerHTML =
|
|
229
|
+
'<span style="color:var(--orange);font-weight:600;font-size:12px;width:100%">Execution paused — plan was updated</span>' +
|
|
230
|
+
'<button onclick="qaReplacePrd(\'' + esc + '\')" style="background:var(--green);color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:11px;font-weight:600;cursor:pointer">Re-execute with new PRD</button>' +
|
|
231
|
+
'<button onclick="this.closest(\'div\').innerHTML=\'<span style=color:var(--muted);font-size:11px>Paused. No work dispatched.</span>\'" style="background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:4px 12px;font-size:11px;cursor:pointer">Keep paused</button>' +
|
|
232
|
+
'<span style="color:var(--muted);font-size:10px;width:100%">Re-execute replaces the old PRD with a fresh one from the updated plan.</span>';
|
|
233
|
+
thread.appendChild(actionDiv);
|
|
234
|
+
}
|
|
221
235
|
} else {
|
|
222
236
|
const qaElapsedErr = Math.round((Date.now() - qaStartTime) / 1000);
|
|
223
237
|
thread.innerHTML += '<div class="modal-qa-a" style="color:var(--red)">Error: ' + escHtml(data.error || 'Failed') + '<div style="font-size:9px;color:var(--muted);margin-top:4px;text-align:right">' + qaElapsedErr + 's</div></div>';
|
|
@@ -215,13 +215,13 @@ function renderPlans(plans) {
|
|
|
215
215
|
const showResume = (effectiveStatus === 'paused' || effectiveStatus === 'awaiting-approval') && prdFile && !isArchived;
|
|
216
216
|
const showVerify = effectiveStatus === 'completed' && prdFile && !isArchived;
|
|
217
217
|
const pauseBtn = showPause ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--yellow)" ' +
|
|
218
|
-
'onclick="event.stopPropagation();planPause(\'' + escHtml(prdFile) + '\')">Pause</button>' : '';
|
|
218
|
+
'onclick="event.stopPropagation();planPause(\'' + escHtml(prdFile) + '\',this)">Pause</button>' : '';
|
|
219
219
|
const resumeBtn = showResume
|
|
220
220
|
? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green)" ' +
|
|
221
|
-
'onclick="event.stopPropagation();planApprove(\'' + escHtml(prdFile) + '\')">' + (effectiveStatus === 'awaiting-approval' ? 'Approve' : 'Resume') + '</button>'
|
|
221
|
+
'onclick="event.stopPropagation();planApprove(\'' + escHtml(prdFile) + '\',this)">' + (effectiveStatus === 'awaiting-approval' ? 'Approve' : 'Resume') + '</button>'
|
|
222
222
|
: '';
|
|
223
223
|
const verifyBtn = showVerify ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green)" ' +
|
|
224
|
-
'onclick="event.stopPropagation();triggerVerify(\'' + escHtml(prdFile) + '\')">Verify</button>' : '';
|
|
224
|
+
'onclick="event.stopPropagation();triggerVerify(\'' + escHtml(prdFile) + '\',this)">Verify</button>' : '';
|
|
225
225
|
const deleteBtn = !isArchived ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red)" ' +
|
|
226
226
|
'onclick="event.stopPropagation();planDelete(\'' + escHtml(p.file) + '\')">Delete</button>' : '';
|
|
227
227
|
|
|
@@ -448,11 +448,11 @@ async function planView(file) {
|
|
|
448
448
|
const modalInProgressLabel = hasActiveWork ? '<span style="font-size:10px;color:var(--blue)">In Progress</span>' : '';
|
|
449
449
|
const isModalCompleted = planStatus === 'completed';
|
|
450
450
|
const modalPauseBtn = isActive && !isMdPlan && !isModalCompleted ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--yellow)" ' +
|
|
451
|
-
'onclick="planPause(\'' + escHtml(normalizedFile) + '\')
|
|
451
|
+
'onclick="planPause(\'' + escHtml(normalizedFile) + '\',this)">Pause</button>' : '';
|
|
452
452
|
const modalResumeBtn = isPaused ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" ' +
|
|
453
|
-
'onclick="planApprove(\'' + escHtml(normalizedFile) + '\')
|
|
453
|
+
'onclick="planApprove(\'' + escHtml(normalizedFile) + '\',this)">Resume</button>' : '';
|
|
454
454
|
const modalVerifyBtn = isModalCompleted ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" ' +
|
|
455
|
-
'onclick="triggerVerify(\'' + escHtml(normalizedFile) + '\')">Verify</button>' : '';
|
|
455
|
+
'onclick="triggerVerify(\'' + escHtml(normalizedFile) + '\',this)">Verify</button>' : '';
|
|
456
456
|
|
|
457
457
|
const lastModLabel = lastMod ? '<div style="font-size:10px;color:var(--muted);font-weight:400;margin-top:2px">Last updated: ' + new Date(lastMod).toLocaleString() + '</div>' : '';
|
|
458
458
|
const actionBtns = '<div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:4px">' +
|
|
@@ -474,17 +474,20 @@ async function planView(file) {
|
|
|
474
474
|
} catch (e) { console.error(e); }
|
|
475
475
|
}
|
|
476
476
|
|
|
477
|
-
async function planApprove(file) {
|
|
477
|
+
async function planApprove(file, btn) {
|
|
478
|
+
if (btn) { btn.dataset.origText = btn.textContent; btn.textContent = 'Approving...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
|
|
478
479
|
try {
|
|
479
480
|
const res = await fetch('/api/plans/approve', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ file }) });
|
|
480
481
|
if (res.ok) {
|
|
481
482
|
showToast('cmd-toast', 'Plan approved — work will begin on next engine tick', true);
|
|
482
483
|
refreshPlans();
|
|
484
|
+
refresh();
|
|
483
485
|
} else {
|
|
486
|
+
if (btn) { btn.textContent = btn.dataset.origText || 'Approve'; btn.style.pointerEvents = ''; btn.style.opacity = ''; }
|
|
484
487
|
const d = await res.json().catch(() => ({}));
|
|
485
488
|
alert('Approve failed: ' + (d.error || 'unknown'));
|
|
486
489
|
}
|
|
487
|
-
} catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); }
|
|
490
|
+
} catch (e) { if (btn) { btn.textContent = btn.dataset.origText || 'Approve'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } showToast('cmd-toast', 'Error: ' + e.message, false); }
|
|
488
491
|
}
|
|
489
492
|
|
|
490
493
|
async function planDelete(file) {
|
|
@@ -506,7 +509,8 @@ async function planDelete(file) {
|
|
|
506
509
|
} catch (e) { alert('Error: ' + e.message); }
|
|
507
510
|
}
|
|
508
511
|
|
|
509
|
-
async function planPause(file) {
|
|
512
|
+
async function planPause(file, btn) {
|
|
513
|
+
if (btn) { btn.dataset.origText = btn.textContent; btn.textContent = 'Pausing...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
|
|
510
514
|
try {
|
|
511
515
|
const res = await fetch('/api/plans/pause', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ file }) });
|
|
512
516
|
if (res.ok) {
|
|
@@ -514,10 +518,11 @@ async function planPause(file) {
|
|
|
514
518
|
refreshPlans();
|
|
515
519
|
refresh();
|
|
516
520
|
} else {
|
|
521
|
+
if (btn) { btn.textContent = btn.dataset.origText || 'Pause'; btn.style.pointerEvents = ''; btn.style.opacity = ''; }
|
|
517
522
|
const d = await res.json().catch(() => ({}));
|
|
518
523
|
alert('Pause failed: ' + (d.error || 'unknown'));
|
|
519
524
|
}
|
|
520
|
-
} catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); }
|
|
525
|
+
} catch (e) { if (btn) { btn.textContent = btn.dataset.origText || 'Pause'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } showToast('cmd-toast', 'Error: ' + e.message, false); }
|
|
521
526
|
}
|
|
522
527
|
|
|
523
528
|
async function planReject(file) {
|
|
@@ -618,7 +623,8 @@ async function openVerifyGuide(file) {
|
|
|
618
623
|
} catch (e) { alert('Failed to load guide: ' + e.message); }
|
|
619
624
|
}
|
|
620
625
|
|
|
621
|
-
async function triggerVerify(file) {
|
|
626
|
+
async function triggerVerify(file, btn) {
|
|
627
|
+
if (btn) { btn.dataset.origText = btn.textContent; btn.textContent = 'Verifying...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
|
|
622
628
|
try {
|
|
623
629
|
const res = await fetch('/api/plans/trigger-verify', {
|
|
624
630
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
@@ -626,13 +632,14 @@ async function triggerVerify(file) {
|
|
|
626
632
|
});
|
|
627
633
|
const d = await res.json();
|
|
628
634
|
if (res.ok && d.ok) {
|
|
629
|
-
closeModal();
|
|
635
|
+
try { closeModal(); } catch { /* may not be open */ }
|
|
630
636
|
refresh();
|
|
631
637
|
showToast('cmd-toast', d.verifyId ? 'Verify task ' + d.verifyId + ' created' : (d.message || 'Done'), true);
|
|
632
638
|
} else {
|
|
639
|
+
if (btn) { btn.textContent = btn.dataset.origText || 'Verify'; btn.style.pointerEvents = ''; btn.style.opacity = ''; }
|
|
633
640
|
alert('Failed: ' + (d.error || 'unknown'));
|
|
634
641
|
}
|
|
635
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
642
|
+
} catch (e) { if (btn) { btn.textContent = btn.dataset.origText || 'Verify'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } alert('Error: ' + e.message); }
|
|
636
643
|
}
|
|
637
644
|
|
|
638
645
|
window.MinionsPlans = { openCreatePlanModal, refreshPlans, derivePlanStatus, renderPlans, openArchivedPlansModal, qaDisablePrdButtons, showPlanVersionActions, qaJustSave, planExecute, planSubmitRevise, planShowRevise, planHideRevise, planView, planApprove, planDelete, planPause, planReject, planDiscuss, planOpenInDocChat, planRegeneratePRD, openVerifyGuide, triggerVerify };
|
|
@@ -31,13 +31,14 @@ function renderPrd(prd, prog) {
|
|
|
31
31
|
let actions = '';
|
|
32
32
|
if (prdFile) {
|
|
33
33
|
if (effectiveStatus === 'awaiting-approval') {
|
|
34
|
-
actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:4px" onclick="planApprove(\'' + escHtml(prdFile) + '\')">Approve</button>';
|
|
34
|
+
actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:4px" onclick="planApprove(\'' + escHtml(prdFile) + '\',this)">Approve</button>';
|
|
35
35
|
} else if (effectiveStatus === 'completed') {
|
|
36
|
-
actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:4px" onclick="triggerVerify(\'' + escHtml(prdFile) + '\')">Verify</button>'
|
|
36
|
+
actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:4px" onclick="triggerVerify(\'' + escHtml(prdFile) + '\',this)">Verify</button>' +
|
|
37
|
+
' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--muted);border-color:var(--border);margin-left:4px" onclick="planDelete(\'' + escHtml(prdFile) + '\')">Archive</button>';
|
|
37
38
|
} else if (effectiveStatus === 'in-progress') {
|
|
38
|
-
actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--yellow);border-color:var(--yellow);margin-left:4px" onclick="planPause(\'' + escHtml(prdFile) + '\')">Pause</button>';
|
|
39
|
+
actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--yellow);border-color:var(--yellow);margin-left:4px" onclick="planPause(\'' + escHtml(prdFile) + '\',this)">Pause</button>';
|
|
39
40
|
} else if (effectiveStatus === 'paused') {
|
|
40
|
-
actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:4px" onclick="planApprove(\'' + escHtml(prdFile) + '\')">Resume</button>';
|
|
41
|
+
actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:4px" onclick="planApprove(\'' + escHtml(prdFile) + '\',this)">Resume</button>';
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -211,14 +212,14 @@ function renderPrdProgress(prog) {
|
|
|
211
212
|
'<span onclick="event.stopPropagation();planView(\'' + escHtml(g.sourcePlan || g.file) + '\')" style="color:var(--blue);cursor:pointer;font-size:10px;padding:2px 8px;background:rgba(56,139,253,0.1);border:1px solid rgba(56,139,253,0.3);border-radius:4px" title="Review latest plan changes">Review plan</span>' +
|
|
212
213
|
'</div>'
|
|
213
214
|
: '';
|
|
214
|
-
const isCompleted = t && t.allDone;
|
|
215
|
+
const isCompleted = (t && t.allDone) || (done > 0 && done === g.items.length);
|
|
215
216
|
const pauseResumeBtn = isAwaitingApproval
|
|
216
|
-
? '<span onclick="event.stopPropagation();planApprove(\'' + escHtml(g.file) + '\')" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Approve</span>'
|
|
217
|
+
? '<span onclick="event.stopPropagation();planApprove(\'' + escHtml(g.file) + '\',this)" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Approve</span>'
|
|
217
218
|
: isPaused
|
|
218
|
-
? '<span onclick="event.stopPropagation();planApprove(\'' + escHtml(g.file) + '\')" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Resume</span>'
|
|
219
|
+
? '<span onclick="event.stopPropagation();planApprove(\'' + escHtml(g.file) + '\',this)" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Resume</span>'
|
|
219
220
|
: isCompleted
|
|
220
|
-
? '<span onclick="event.stopPropagation();triggerVerify(\'' + escHtml(g.file) + '\')" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Verify</span>'
|
|
221
|
-
: '<span onclick="event.stopPropagation();planPause(\'' + escHtml(g.file) + '\')" style="color:var(--yellow);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.3);border-radius:3px">Pause</span>';
|
|
221
|
+
? '<span onclick="event.stopPropagation();triggerVerify(\'' + escHtml(g.file) + '\',this)" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Verify</span>'
|
|
222
|
+
: '<span onclick="event.stopPropagation();planPause(\'' + escHtml(g.file) + '\',this)" style="color:var(--yellow);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.3);border-radius:3px">Pause</span>';
|
|
222
223
|
const deleteBtn = '<span onclick="event.stopPropagation();planDelete(\'' + escHtml(g.file) + '\')" style="color:var(--red);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(248,81,73,0.1);border:1px solid rgba(248,81,73,0.3);border-radius:3px">Delete</span>';
|
|
223
224
|
const sourcePlanLink = g.sourcePlan
|
|
224
225
|
? '<span onclick="event.stopPropagation();planView(\'' + escHtml(g.sourcePlan) + '\')" style="color:var(--blue);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(56,139,253,0.1);border:1px solid rgba(56,139,253,0.3);border-radius:3px" title="View source plan">📄 Plan</span>'
|
package/dashboard.js
CHANGED
|
@@ -2349,10 +2349,91 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
2349
2349
|
}
|
|
2350
2350
|
}
|
|
2351
2351
|
if (canEdit && fullPath) {
|
|
2352
|
-
// Always save in-place — the engine's staleness detection handles PRD sync
|
|
2353
|
-
// if the source plan changes while an active PRD is running.
|
|
2354
2352
|
safeWrite(fullPath, content);
|
|
2355
|
-
|
|
2353
|
+
|
|
2354
|
+
// If editing a plan .md that has an active PRD, auto-pause execution
|
|
2355
|
+
let pausedPrd = null;
|
|
2356
|
+
if (body.filePath && body.filePath.startsWith('plans/') && body.filePath.endsWith('.md')) {
|
|
2357
|
+
const planFile = body.filePath.replace(/^plans\//, '');
|
|
2358
|
+
try {
|
|
2359
|
+
const prdDir = path.join(MINIONS_DIR, 'prd');
|
|
2360
|
+
if (fs.existsSync(prdDir)) {
|
|
2361
|
+
for (const f of fs.readdirSync(prdDir)) {
|
|
2362
|
+
if (!f.endsWith('.json')) continue;
|
|
2363
|
+
const prd = safeJson(path.join(prdDir, f));
|
|
2364
|
+
if (!prd || prd.source_plan !== planFile) continue;
|
|
2365
|
+
if (prd.status === 'paused' || prd.status === 'rejected') continue;
|
|
2366
|
+
// Found an active PRD linked to this plan — pause it
|
|
2367
|
+
prd.status = 'paused';
|
|
2368
|
+
prd.pausedAt = new Date().toISOString();
|
|
2369
|
+
prd.pausedBy = 'plan-steering';
|
|
2370
|
+
safeWrite(path.join(prdDir, f), prd);
|
|
2371
|
+
pausedPrd = f;
|
|
2372
|
+
// Pause work items (reuse pause logic inline)
|
|
2373
|
+
const wiPaths = [path.join(MINIONS_DIR, 'work-items.json')];
|
|
2374
|
+
for (const proj of PROJECTS) wiPaths.push(shared.projectWorkItemsPath(proj));
|
|
2375
|
+
const dispatchPath = path.join(MINIONS_DIR, 'engine', 'dispatch.json');
|
|
2376
|
+
const dispatch = JSON.parse(safeRead(dispatchPath) || '{}');
|
|
2377
|
+
const killedAgents = new Set();
|
|
2378
|
+
const resetItemIds = new Set();
|
|
2379
|
+
for (const wiPath of wiPaths) {
|
|
2380
|
+
try {
|
|
2381
|
+
const items = safeJson(wiPath);
|
|
2382
|
+
if (!items) continue;
|
|
2383
|
+
let changed = false;
|
|
2384
|
+
for (const w of items) {
|
|
2385
|
+
if (w.sourcePlan !== f) continue;
|
|
2386
|
+
if (w.status === 'done' || w.status === 'implemented' || w.status === 'complete' || w.status === 'in-pr') continue;
|
|
2387
|
+
if (w.status === 'dispatched') {
|
|
2388
|
+
const activeEntry = (dispatch.active || []).find(d => d.meta?.item?.id === w.id || d.meta?.dispatchKey?.includes(w.id));
|
|
2389
|
+
if (activeEntry) {
|
|
2390
|
+
const statusPath = path.join(MINIONS_DIR, 'agents', activeEntry.agent, 'status.json');
|
|
2391
|
+
try {
|
|
2392
|
+
const agentStatus = JSON.parse(safeRead(statusPath) || '{}');
|
|
2393
|
+
if (agentStatus.pid) {
|
|
2394
|
+
if (process.platform === 'win32') {
|
|
2395
|
+
try { require('child_process').execSync('taskkill /PID ' + agentStatus.pid + ' /F /T', { stdio: 'pipe', timeout: 5000 }); } catch { /* process may be dead */ }
|
|
2396
|
+
} else {
|
|
2397
|
+
try { process.kill(agentStatus.pid, 'SIGTERM'); } catch { /* process may be dead */ }
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
agentStatus.status = 'idle';
|
|
2401
|
+
delete agentStatus.currentTask;
|
|
2402
|
+
delete agentStatus.dispatched;
|
|
2403
|
+
safeWrite(statusPath, agentStatus);
|
|
2404
|
+
} catch { /* agent reset */ }
|
|
2405
|
+
killedAgents.add(activeEntry.agent);
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
w.status = 'pending';
|
|
2409
|
+
delete w.dispatched_at;
|
|
2410
|
+
delete w.dispatched_to;
|
|
2411
|
+
delete w.failReason;
|
|
2412
|
+
delete w.failedAt;
|
|
2413
|
+
changed = true;
|
|
2414
|
+
if (w.id) resetItemIds.add(w.id);
|
|
2415
|
+
}
|
|
2416
|
+
if (changed) safeWrite(wiPath, items);
|
|
2417
|
+
} catch { /* reset work items */ }
|
|
2418
|
+
}
|
|
2419
|
+
if (resetItemIds.size > 0 || killedAgents.size > 0) {
|
|
2420
|
+
mutateJsonFileLocked(dispatchPath, (dp) => {
|
|
2421
|
+
dp.active = (dp.active || []).filter(d => {
|
|
2422
|
+
if (d.meta?.item?.id && resetItemIds.has(d.meta.item.id)) return false;
|
|
2423
|
+
if (killedAgents.has(d.agent)) return false;
|
|
2424
|
+
return true;
|
|
2425
|
+
});
|
|
2426
|
+
return dp;
|
|
2427
|
+
}, { defaultValue: { pending: [], active: [], completed: [] } });
|
|
2428
|
+
}
|
|
2429
|
+
invalidateStatusCache();
|
|
2430
|
+
break;
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
} catch (e) { console.error('auto-pause PRD on plan steer:', e.message); }
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
return jsonReply(res, 200, { ok: true, answer, edited: true, content, actions, pausedPrd });
|
|
2356
2437
|
}
|
|
2357
2438
|
return jsonReply(res, 200, { ok: true, answer: answer + '\n\n(Read-only — changes not saved)', edited: false, actions });
|
|
2358
2439
|
} catch (e) { return jsonReply(res, 500, { error: e.message }); }
|
package/engine/queries.js
CHANGED
|
@@ -570,6 +570,11 @@ function getPrdInfo(config) {
|
|
|
570
570
|
for (const wi of workItems) { if (wi.sourcePlan) wiById[wi.id] = wi; }
|
|
571
571
|
} catch { /* optional */ }
|
|
572
572
|
}
|
|
573
|
+
// Also check central work-items.json
|
|
574
|
+
try {
|
|
575
|
+
const centralWi = safeJson(path.join(MINIONS_DIR, 'work-items.json')) || [];
|
|
576
|
+
for (const wi of centralWi) { if (wi.sourcePlan && !wiById[wi.id]) wiById[wi.id] = wi; }
|
|
577
|
+
} catch { /* optional */ }
|
|
573
578
|
|
|
574
579
|
// PR-to-PRD linking — primary source is pr-links.json (single-writer, never clobbered by polling)
|
|
575
580
|
const allPrs = getPullRequests(config);
|
|
@@ -600,7 +605,9 @@ function getPrdInfo(config) {
|
|
|
600
605
|
const statusDisplay = { dispatched: 'in-progress', pending: 'missing' };
|
|
601
606
|
for (const item of items) {
|
|
602
607
|
const wi = wiById[item.id];
|
|
603
|
-
|
|
608
|
+
// Work item status is source of truth when available (PRD JSON may lag behind)
|
|
609
|
+
const rawStatus = wi ? (wi.status || item.status) : item.status;
|
|
610
|
+
item.status = statusDisplay[rawStatus] || rawStatus || 'missing';
|
|
604
611
|
// Attach execution metadata for display (agent, PR link, fail reason)
|
|
605
612
|
if (wi) {
|
|
606
613
|
if (wi.dispatched_to) item._agent = wi.dispatched_to;
|
|
@@ -616,20 +623,15 @@ function getPrdInfo(config) {
|
|
|
616
623
|
const missing = (byStatus['missing'] || []).length;
|
|
617
624
|
const donePercent = total > 0 ? Math.round((complete / total) * 100) : 0;
|
|
618
625
|
|
|
619
|
-
// Plan timings
|
|
626
|
+
// Plan timings — use wiById (already includes central work-items.json)
|
|
620
627
|
const planTimings = {};
|
|
621
|
-
for (const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if (wi.dispatched_at) { const d = new Date(wi.dispatched_at).getTime(); if (!t.firstDispatched || d < t.firstDispatched) t.firstDispatched = d; }
|
|
629
|
-
if (wi.completedAt) { const c = new Date(wi.completedAt).getTime(); if (!t.lastCompleted || c > t.lastCompleted) t.lastCompleted = c; }
|
|
630
|
-
if (wi.status !== 'done' && wi.status !== 'in-pr') t.allDone = false; // in-pr treated as done for backward compat
|
|
631
|
-
}
|
|
632
|
-
} catch { /* optional */ }
|
|
628
|
+
for (const wi of Object.values(wiById)) {
|
|
629
|
+
if (!wi.sourcePlan) continue;
|
|
630
|
+
if (!planTimings[wi.sourcePlan]) planTimings[wi.sourcePlan] = { firstDispatched: null, lastCompleted: null, allDone: true };
|
|
631
|
+
const t = planTimings[wi.sourcePlan];
|
|
632
|
+
if (wi.dispatched_at) { const d = new Date(wi.dispatched_at).getTime(); if (!t.firstDispatched || d < t.firstDispatched) t.firstDispatched = d; }
|
|
633
|
+
if (wi.completedAt) { const c = new Date(wi.completedAt).getTime(); if (!t.lastCompleted || c > t.lastCompleted) t.lastCompleted = c; }
|
|
634
|
+
if (wi.status !== 'done' && wi.status !== 'in-pr') t.allDone = false; // in-pr treated as done for backward compat
|
|
633
635
|
}
|
|
634
636
|
|
|
635
637
|
const progress = {
|
package/package.json
CHANGED