@yemi33/minions 0.1.152 → 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 +28 -0
- package/dashboard/js/command-center.js +3 -5
- package/dashboard/js/modal-qa.js +3 -45
- package/dashboard/js/modal.js +0 -2
- package/dashboard/js/refresh.js +44 -0
- package/dashboard/js/render-inbox.js +0 -1
- package/dashboard/js/render-kb.js +0 -1
- package/dashboard/js/render-plans.js +2 -44
- package/dashboard/js/render-prd.js +10 -12
- package/dashboard/js/render-work-items.js +42 -8
- package/dashboard/styles.css +4 -2
- package/dashboard.html +8 -103
- package/dashboard.js +2 -144
- package/docs/deprecated.json +1 -83
- package/engine/lifecycle.js +72 -57
- package/engine/playbook.js +1 -1
- package/engine/queries.js +2 -4
- package/engine.js +5 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.154 (2026-04-02)
|
|
4
|
+
|
|
5
|
+
### Engine
|
|
6
|
+
- engine.js
|
|
7
|
+
- engine/lifecycle.js
|
|
8
|
+
- engine/playbook.js
|
|
9
|
+
- engine/queries.js
|
|
10
|
+
|
|
11
|
+
### Dashboard
|
|
12
|
+
- dashboard.html
|
|
13
|
+
- dashboard.js
|
|
14
|
+
- dashboard/js/command-center.js
|
|
15
|
+
- dashboard/js/modal-qa.js
|
|
16
|
+
- dashboard/js/modal.js
|
|
17
|
+
- dashboard/js/refresh.js
|
|
18
|
+
- dashboard/js/render-inbox.js
|
|
19
|
+
- dashboard/js/render-kb.js
|
|
20
|
+
- dashboard/js/render-plans.js
|
|
21
|
+
- dashboard/js/render-prd.js
|
|
22
|
+
- dashboard/js/render-work-items.js
|
|
23
|
+
- dashboard/styles.css
|
|
24
|
+
|
|
25
|
+
### Documentation
|
|
26
|
+
- deprecated.json
|
|
27
|
+
|
|
28
|
+
### Other
|
|
29
|
+
- test/unit.test.js
|
|
30
|
+
|
|
3
31
|
## 0.1.152 (2026-04-02)
|
|
4
32
|
|
|
5
33
|
### Dashboard
|
|
@@ -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/modal-qa.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
let _modalDocContext = { title: '', content: '', selection: '' };
|
|
4
4
|
let _modalFilePath = null; // file path for steering (null = read-only Q&A only)
|
|
5
|
-
let _modalOriginalPlan = null; // tracks original plan file when editing a forked version
|
|
6
5
|
|
|
7
6
|
function showModalQa() {
|
|
8
7
|
document.getElementById('modal-qa').style.display = '';
|
|
@@ -235,9 +234,9 @@ async function _processQaMessage(message, selection) {
|
|
|
235
234
|
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';
|
|
236
235
|
actionDiv.innerHTML =
|
|
237
236
|
'<span style="color:var(--orange);font-weight:600;font-size:12px;width:100%">Execution paused — plan was updated</span>' +
|
|
238
|
-
'<button onclick="
|
|
237
|
+
'<button onclick="planExecute(\'' + esc + '\', \'\', null)" style="background:var(--green);color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:11px;font-weight:600;cursor:pointer">Re-execute plan</button>' +
|
|
239
238
|
'<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>' +
|
|
240
|
-
'<span style="color:var(--muted);font-size:10px;width:100%">Re-execute
|
|
239
|
+
'<span style="color:var(--muted);font-size:10px;width:100%">Re-execute creates a new PRD from the updated plan.</span>';
|
|
241
240
|
thread.appendChild(actionDiv);
|
|
242
241
|
}
|
|
243
242
|
} else {
|
|
@@ -289,45 +288,4 @@ async function _processQaMessage(message, selection) {
|
|
|
289
288
|
}
|
|
290
289
|
}
|
|
291
290
|
|
|
292
|
-
|
|
293
|
-
qaDisablePrdButtons();
|
|
294
|
-
const allPlans = window._lastStatus?.plans || [];
|
|
295
|
-
const mdPlan = allPlans.find(p => p.file === planFile);
|
|
296
|
-
const project = mdPlan?.project || '';
|
|
297
|
-
|
|
298
|
-
planExecute(planFile, project, null);
|
|
299
|
-
|
|
300
|
-
const btn = document.getElementById('qa-generate-prd-btn');
|
|
301
|
-
if (btn) btn.innerHTML = '<span style="color:var(--green);font-size:12px">New PRD dispatched — existing work continues, agent creating fresh PRD from revised plan.</span>';
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
async function qaReplacePrd(planFile) {
|
|
305
|
-
qaDisablePrdButtons();
|
|
306
|
-
const allPlans = window._lastStatus?.plans || [];
|
|
307
|
-
const mdPlan = allPlans.find(p => p.file === planFile);
|
|
308
|
-
const project = mdPlan?.project || '';
|
|
309
|
-
const existingPrd = allPlans.find(p => p.file.endsWith('.json') && p.project === project);
|
|
310
|
-
|
|
311
|
-
if (existingPrd) {
|
|
312
|
-
// Pause first to stop materialization, then clean pending items
|
|
313
|
-
try {
|
|
314
|
-
await fetch('/api/plans/pause', {
|
|
315
|
-
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
316
|
-
body: JSON.stringify({ file: existingPrd.file })
|
|
317
|
-
});
|
|
318
|
-
} catch (e) { console.error('plan pause:', e.message); }
|
|
319
|
-
try {
|
|
320
|
-
await fetch('/api/plans/regenerate', {
|
|
321
|
-
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
322
|
-
body: JSON.stringify({ source: existingPrd.file })
|
|
323
|
-
});
|
|
324
|
-
} catch (e) { console.error('plan regenerate:', e.message); }
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
planExecute(planFile, project, null);
|
|
328
|
-
|
|
329
|
-
const btn = document.getElementById('qa-generate-prd-btn');
|
|
330
|
-
if (btn) btn.innerHTML = '<span style="color:var(--orange);font-size:12px">Replacing PRD — old items paused, agent regenerating from revised plan.</span>';
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
window.MinionsQA = { showModalQa, modalAskAboutSelection, clearQaSelection, clearQaConversation, modalSend, qaNewPrd, qaReplacePrd };
|
|
291
|
+
window.MinionsQA = { showModalQa, modalAskAboutSelection, clearQaSelection, clearQaConversation, modalSend };
|
package/dashboard/js/modal.js
CHANGED
|
@@ -35,8 +35,6 @@ function closeModal() {
|
|
|
35
35
|
// Clear edit/steer state
|
|
36
36
|
_modalEditable = null;
|
|
37
37
|
_modalFilePath = null;
|
|
38
|
-
_modalOriginalPlan = null;
|
|
39
|
-
// steer btn removed — unified send
|
|
40
38
|
const body = document.getElementById('modal-body');
|
|
41
39
|
body.contentEditable = 'false';
|
|
42
40
|
body.style.border = '';
|
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) {
|
|
@@ -72,7 +72,6 @@ function openNotesModal() {
|
|
|
72
72
|
_modalEditable = 'notes.md';
|
|
73
73
|
_modalFilePath = 'notes.md'; showModalQa();
|
|
74
74
|
document.getElementById('modal-edit-btn').style.display = '';
|
|
75
|
-
// steer btn removed — unified send
|
|
76
75
|
document.getElementById('modal').classList.add('open');
|
|
77
76
|
}
|
|
78
77
|
|
|
@@ -169,7 +169,6 @@ async function kbOpenItem(category, file) {
|
|
|
169
169
|
// Clear notification badge when opening this document
|
|
170
170
|
const card = findCardForFile(_modalFilePath);
|
|
171
171
|
if (card) clearNotifBadge(card);
|
|
172
|
-
// steer btn removed — unified send
|
|
173
172
|
document.getElementById('modal').classList.add('open');
|
|
174
173
|
} catch (e) {
|
|
175
174
|
console.error('Failed to load KB item:', e);
|
|
@@ -67,7 +67,7 @@ function derivePlanStatus(prdFile, mdFile, prdJsonStatus, workItems) {
|
|
|
67
67
|
const hasPendingPrd = wi.some(w => w.type === 'plan-to-prd' && (w.status === 'pending' || w.status === 'dispatched'));
|
|
68
68
|
const hasActiveWork = implementWi.some(w => w.status === 'pending' || w.status === 'dispatched');
|
|
69
69
|
const allDone = implementWi.length > 0 && implementWi.every(w =>
|
|
70
|
-
w.status === 'done'
|
|
70
|
+
w.status === 'done'
|
|
71
71
|
);
|
|
72
72
|
const hasFailed = implementWi.some(w => w.status === 'failed');
|
|
73
73
|
|
|
@@ -304,48 +304,6 @@ function openArchivedPlansModal() {
|
|
|
304
304
|
document.getElementById('modal').classList.add('open');
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
// Disable all PRD action buttons to prevent double-clicks
|
|
308
|
-
function qaDisablePrdButtons() {
|
|
309
|
-
const container = document.getElementById('qa-generate-prd-btn');
|
|
310
|
-
if (container) container.querySelectorAll('button').forEach(b => { b.disabled = true; b.style.opacity = '0.5'; });
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Show plan version action buttons (Run alongside / Replace / Just save)
|
|
314
|
-
function showPlanVersionActions(thread, newFile, originalFile) {
|
|
315
|
-
const esc = newFile.replace(/'/g, "\\'");
|
|
316
|
-
// Look up existing PRD for the original plan's project
|
|
317
|
-
const allPlans = window._lastStatus?.plans || [];
|
|
318
|
-
const origPlan = allPlans.find(p => p.file === originalFile);
|
|
319
|
-
const project = origPlan?.project || '';
|
|
320
|
-
const existingPrd = allPlans.find(p => p.file.endsWith('.json') && p.project === project && p.status !== 'completed');
|
|
321
|
-
|
|
322
|
-
const btn = document.createElement('div');
|
|
323
|
-
btn.id = 'qa-generate-prd-btn';
|
|
324
|
-
btn.style.cssText = 'margin:8px 0;padding:8px 12px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:6px;display:flex;flex-wrap:wrap;align-items:center;gap:8px';
|
|
325
|
-
|
|
326
|
-
if (existingPrd) {
|
|
327
|
-
btn.innerHTML = '<span style="color:var(--green);font-weight:600;font-size:12px;width:100%">New plan version created — existing PRD running</span>' +
|
|
328
|
-
'<button onclick="qaNewPrd(\'' + esc + '\')" style="background:var(--green);color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:11px;font-weight:600;cursor:pointer" title="Execute this plan as a separate PRD alongside the current one">Run alongside</button>' +
|
|
329
|
-
'<button onclick="qaReplacePrd(\'' + esc + '\')" style="background:var(--orange);color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:11px;font-weight:600;cursor:pointer" title="Pause existing PRD, clean pending items, execute this plan instead">Replace old PRD</button>' +
|
|
330
|
-
'<button onclick="qaJustSave(this)" style="background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:4px 12px;font-size:11px;cursor:pointer" title="Keep the new version saved without dispatching any work">Just save</button>' +
|
|
331
|
-
'<span style="color:var(--muted);font-size:10px;width:100%">Run alongside keeps current work going. Replace pauses it and starts fresh.</span>';
|
|
332
|
-
} else {
|
|
333
|
-
btn.innerHTML = '<span style="color:var(--green);font-weight:600;font-size:12px;width:100%">New plan version created</span>' +
|
|
334
|
-
'<button onclick="qaNewPrd(\'' + esc + '\')" style="background:var(--green);color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:11px;font-weight:600;cursor:pointer">Execute plan</button>' +
|
|
335
|
-
'<button onclick="qaJustSave(this)" style="background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:4px 12px;font-size:11px;cursor:pointer">Just save</button>' +
|
|
336
|
-
'<span style="color:var(--muted);font-size:10px">Execute dispatches an agent to create PRD items from this plan</span>';
|
|
337
|
-
}
|
|
338
|
-
// Remove any previous action buttons
|
|
339
|
-
const old = thread.querySelector('#qa-generate-prd-btn');
|
|
340
|
-
if (old) old.remove();
|
|
341
|
-
thread.appendChild(btn);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function qaJustSave(el) {
|
|
345
|
-
const container = el.closest('#qa-generate-prd-btn');
|
|
346
|
-
if (container) container.innerHTML = '<span style="color:var(--muted);font-size:11px">Saved. No work dispatched.</span>';
|
|
347
|
-
}
|
|
348
|
-
|
|
349
307
|
async function planExecute(file, project, btn) {
|
|
350
308
|
if (btn) { btn.textContent = 'Executing...'; btn.disabled = true; btn.style.color = 'var(--blue)'; }
|
|
351
309
|
try {
|
|
@@ -736,4 +694,4 @@ async function planUnarchive(file, btn) {
|
|
|
736
694
|
} catch (e) { resetBtn(); alert('Error: ' + e.message); }
|
|
737
695
|
}
|
|
738
696
|
|
|
739
|
-
window.MinionsPlans = { openCreatePlanModal, refreshPlans, derivePlanStatus, renderPlans, openArchivedPlansModal,
|
|
697
|
+
window.MinionsPlans = { openCreatePlanModal, refreshPlans, derivePlanStatus, renderPlans, openArchivedPlansModal, planExecute, planSubmitRevise, planShowRevise, planHideRevise, planView, planApprove, planArchive, planUnarchive, planDelete, planPause, planReject, planDiscuss, planOpenInDocChat, planRegeneratePRD, openVerifyGuide, triggerVerify };
|
|
@@ -24,7 +24,7 @@ function renderPrd(prd, prog) {
|
|
|
24
24
|
if (existing.length <= 1) {
|
|
25
25
|
// Single PRD — show status + actions in header
|
|
26
26
|
const implementItems = allWi.filter(w => prdItems.some(pi => pi.id === w.id));
|
|
27
|
-
const allDone = implementItems.length > 0 && implementItems.every(w => w.status === 'done'
|
|
27
|
+
const allDone = implementItems.length > 0 && implementItems.every(w => w.status === 'done');
|
|
28
28
|
const hasActive = implementItems.some(w => w.status === 'pending' || w.status === 'dispatched');
|
|
29
29
|
const prdFile = existing[0]?.file || '';
|
|
30
30
|
const prdStatus = existing[0]?.status || '';
|
|
@@ -52,7 +52,7 @@ function renderPrd(prd, prog) {
|
|
|
52
52
|
for (const p of existing) {
|
|
53
53
|
const items = prdItems.filter(i => i.source === p.file);
|
|
54
54
|
const wiForPrd = allWi.filter(w => items.some(pi => pi.id === w.id));
|
|
55
|
-
const allDone = wiForPrd.length > 0 && wiForPrd.every(w => w.status === 'done'
|
|
55
|
+
const allDone = wiForPrd.length > 0 && wiForPrd.every(w => w.status === 'done');
|
|
56
56
|
const hasActive = wiForPrd.some(w => w.status === 'pending' || w.status === 'dispatched');
|
|
57
57
|
const s = allDone && !hasActive ? 'completed' : hasActive ? 'in-progress' : p.status || 'active';
|
|
58
58
|
counts[s] = (counts[s] || 0) + 1;
|
|
@@ -73,7 +73,7 @@ function renderPrdProgress(prog) {
|
|
|
73
73
|
// Compute overall progress from active (non-archived) items
|
|
74
74
|
const activeItems = (prog.items || []).filter(i => !i._archived);
|
|
75
75
|
if (activeItems.length > 0) {
|
|
76
|
-
const activeDone = activeItems.filter(i => i.status === 'done'
|
|
76
|
+
const activeDone = activeItems.filter(i => i.status === 'done').length;
|
|
77
77
|
countEl.textContent = Math.round((activeDone / activeItems.length) * 100) + '%';
|
|
78
78
|
} else {
|
|
79
79
|
countEl.textContent = '—';
|
|
@@ -82,7 +82,7 @@ function renderPrdProgress(prog) {
|
|
|
82
82
|
function renderGroupStats(items) {
|
|
83
83
|
const total = items.length;
|
|
84
84
|
if (total === 0) return '';
|
|
85
|
-
const done = items.filter(i => i.status === 'done'
|
|
85
|
+
const done = items.filter(i => i.status === 'done').length;
|
|
86
86
|
const inProgress = items.filter(i => i.status === 'in-progress').length;
|
|
87
87
|
const failed = items.filter(i => i.status === 'failed').length;
|
|
88
88
|
const paused = items.filter(i => i.status === 'paused').length;
|
|
@@ -112,13 +112,11 @@ function renderPrdProgress(prog) {
|
|
|
112
112
|
const statusBadge = (s) => {
|
|
113
113
|
const styles = {
|
|
114
114
|
'done': 'background:rgba(63,185,80,0.15);color:var(--green)',
|
|
115
|
-
'implemented': 'background:rgba(63,185,80,0.15);color:var(--green)', /* legacy alias */
|
|
116
|
-
'in-pr': 'background:rgba(63,185,80,0.15);color:var(--green)', /* backward compat: displayed as done */
|
|
117
115
|
'in-progress': 'background:rgba(210,153,34,0.15);color:var(--yellow);animation:wipPulse 1.5s infinite',
|
|
118
116
|
'failed': 'background:rgba(248,81,73,0.15);color:var(--red)',
|
|
119
117
|
'paused': 'background:rgba(139,148,158,0.15);color:var(--muted)',
|
|
120
118
|
};
|
|
121
|
-
const labels = { 'done': 'DONE', '
|
|
119
|
+
const labels = { 'done': 'DONE', 'in-progress': 'WIP', 'failed': 'FAIL', 'paused': 'PAUSED', 'missing': '—' };
|
|
122
120
|
const style = styles[s] || 'background:var(--surface);color:var(--muted)';
|
|
123
121
|
const label = labels[s] || '—';
|
|
124
122
|
return '<span style="font-size:9px;font-weight:700;padding:2px 6px;border-radius:3px;letter-spacing:0.5px;white-space:nowrap;' + style + '">' + label + '</span>';
|
|
@@ -197,7 +195,7 @@ function renderPrdProgress(prog) {
|
|
|
197
195
|
const timings = prog.planTimings || {};
|
|
198
196
|
|
|
199
197
|
const renderGroupHeader = (g) => {
|
|
200
|
-
const done = g.items.filter(i => i.status === 'done'
|
|
198
|
+
const done = g.items.filter(i => i.status === 'done').length;
|
|
201
199
|
const wip = g.items.filter(i => i.status === 'in-progress' || i.status === 'dispatched').length;
|
|
202
200
|
const summary = (g.summary || '').replace(/^Convert plan to PRD:\s*/i, '').slice(0, 80);
|
|
203
201
|
const isAwaitingApproval = g.planStatus === 'awaiting-approval';
|
|
@@ -304,7 +302,7 @@ function renderPrdProgress(prog) {
|
|
|
304
302
|
});
|
|
305
303
|
|
|
306
304
|
const statusColor = (s) => {
|
|
307
|
-
if (s === 'done'
|
|
305
|
+
if (s === 'done') return 'var(--green)';
|
|
308
306
|
if (s === 'in-progress') return 'var(--yellow)';
|
|
309
307
|
if (s === 'failed') return 'var(--red)';
|
|
310
308
|
if (s === 'paused') return 'var(--muted)';
|
|
@@ -475,7 +473,7 @@ function openArchivedPrdModal() {
|
|
|
475
473
|
// Picker: list archived plans, click to expand
|
|
476
474
|
html = '<div style="margin-bottom:12px;font-size:12px;color:var(--muted)">Select an archived PRD to view:</div>';
|
|
477
475
|
html += groups.map((g, i) => {
|
|
478
|
-
const done = g.items.filter(it => it.status === 'done'
|
|
476
|
+
const done = g.items.filter(it => it.status === 'done').length;
|
|
479
477
|
const failed = g.items.filter(it => it.status === 'failed').length;
|
|
480
478
|
return '<div class="plan-card" style="cursor:pointer;margin-bottom:8px" onclick="showArchivedPrdDetail(' + i + ')">' +
|
|
481
479
|
'<div class="plan-card-title" style="font-size:13px">' + escHtml(g.summary || g.file) + '</div>' +
|
|
@@ -538,7 +536,7 @@ async function prdItemEdit(source, itemId) {
|
|
|
538
536
|
|
|
539
537
|
// Build completion summary section
|
|
540
538
|
let completionHtml = '';
|
|
541
|
-
const isDone = item.status === 'done'
|
|
539
|
+
const isDone = item.status === 'done';
|
|
542
540
|
const isFailed = item.status === 'failed';
|
|
543
541
|
const isActive = item.status === 'in-progress' || item.status === 'dispatched';
|
|
544
542
|
|
|
@@ -711,7 +709,7 @@ function openArchive(i) {
|
|
|
711
709
|
html += '<div class="archive-feature">' +
|
|
712
710
|
'<span class="feat-id">' + escHtml(f.id) + '</span> ' +
|
|
713
711
|
'<span class="prd-item-priority ' + pClass + '">' + escHtml(f.priority || '') + '</span>' +
|
|
714
|
-
(f.status ? ' <span class="pr-badge ' + (f.status === '
|
|
712
|
+
(f.status ? ' <span class="pr-badge ' + (f.status === 'done' ? 'approved' : 'draft') + '" style="font-size:9px">' + escHtml(f.status) + '</span>' : '') +
|
|
715
713
|
'<div class="feat-name">' + escHtml(f.name) + '</div>' +
|
|
716
714
|
'<div class="feat-desc">' + escHtml(f.description || '') + '</div>' +
|
|
717
715
|
(f.rationale ? '<div class="feat-desc" style="margin-top:4px;color:var(--yellow)">Rationale: ' + escHtml(f.rationale) + '</div>' : '') +
|
|
@@ -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
|
@@ -181,8 +181,6 @@
|
|
|
181
181
|
.prd-items-list { display: flex; flex-direction: column; gap: 3px; max-height: 400px; overflow-y: auto; padding: 0 8px; }
|
|
182
182
|
.prd-item-row { display: flex; align-items: center; gap: 8px; padding: 4px 8px; border-radius: var(--radius-sm); font-size: var(--text-base); background: var(--surface2); border: 1px solid var(--border); border-left: 3px solid var(--border); }
|
|
183
183
|
.prd-item-row.st-done { border-left-color: var(--green); }
|
|
184
|
-
.prd-item-row.st-implemented { border-left-color: var(--green); } /* legacy alias */
|
|
185
|
-
.prd-item-row.st-in-pr { border-left-color: var(--green); } /* backward compat: treated as done */
|
|
186
184
|
.prd-item-row.st-in-progress { border-left-color: var(--yellow); animation: prdWipPulse 2s infinite; }
|
|
187
185
|
@keyframes prdWipPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(210,153,34,0); } 50% { box-shadow: 0 0 0 4px rgba(210,153,34,0.2); } }
|
|
188
186
|
.prd-item-row.st-failed { border-left-color: var(--red); }
|
|
@@ -507,6 +505,10 @@
|
|
|
507
505
|
.dispatch-type.plan { background: rgba(168,85,247,0.15); color: #a855f7; }
|
|
508
506
|
.dispatch-type.plan-to-prd { background: rgba(168,85,247,0.1); color: #a855f7; }
|
|
509
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); }
|
|
510
512
|
.dispatch-type.manual { background: rgba(139,148,158,0.15); color: var(--muted); }
|
|
511
513
|
.dispatch-agent { font-weight: 600; color: var(--text); }
|
|
512
514
|
.dispatch-task { flex: 1; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|