@yemi33/minions 0.1.149 → 0.1.151
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 +18 -0
- package/dashboard/js/command-center.js +27 -1
- package/dashboard/js/render-meetings.js +29 -16
- package/dashboard/js/render-plans.js +6 -8
- package/engine/lifecycle.js +12 -12
- package/engine.js +2 -2
- package/package.json +1 -1
- package/playbooks/work-item.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.151 (2026-04-02)
|
|
4
|
+
|
|
5
|
+
### Engine
|
|
6
|
+
- engine.js
|
|
7
|
+
- engine/lifecycle.js
|
|
8
|
+
|
|
9
|
+
### Dashboard
|
|
10
|
+
- dashboard/js/command-center.js
|
|
11
|
+
- dashboard/js/render-meetings.js
|
|
12
|
+
|
|
13
|
+
### Playbooks
|
|
14
|
+
- work-item.md
|
|
15
|
+
|
|
16
|
+
## 0.1.150 (2026-04-01)
|
|
17
|
+
|
|
18
|
+
### Dashboard
|
|
19
|
+
- dashboard/js/render-plans.js
|
|
20
|
+
|
|
3
21
|
## 0.1.149 (2026-04-01)
|
|
4
22
|
|
|
5
23
|
### Engine
|
|
@@ -37,6 +37,28 @@ function ccRestoreMessages() {
|
|
|
37
37
|
for (const msg of _ccMessages) {
|
|
38
38
|
ccAddMessage(msg.role, msg.html, true);
|
|
39
39
|
}
|
|
40
|
+
// Restore "thinking" indicator if CC was mid-request when page refreshed
|
|
41
|
+
try {
|
|
42
|
+
const sendingState = JSON.parse(localStorage.getItem('cc-sending') || 'null');
|
|
43
|
+
if (sendingState?.sending && (Date.now() - sendingState.startedAt) < 300000) {
|
|
44
|
+
_ccSending = true;
|
|
45
|
+
const elapsed = Date.now() - sendingState.startedAt;
|
|
46
|
+
const thinking = document.createElement('div');
|
|
47
|
+
thinking.id = 'cc-thinking';
|
|
48
|
+
thinking.style.cssText = 'padding:8px 12px;border-radius:8px;font-size:11px;color:var(--muted);align-self:flex-start;display:flex;align-items:center;gap:8px';
|
|
49
|
+
thinking.innerHTML = '<span class="dot-pulse" style="display:inline-flex;gap:3px"><span style="width:4px;height:4px;background:var(--blue);border-radius:50%;animation:dotPulse 1.2s infinite"></span><span style="width:4px;height:4px;background:var(--blue);border-radius:50%;animation:dotPulse 1.2s infinite;animation-delay:0.2s"></span><span style="width:4px;height:4px;background:var(--blue);border-radius:50%;animation:dotPulse 1.2s infinite;animation-delay:0.4s"></span></span> <span id="cc-thinking-text">Still working...</span> <span id="cc-thinking-time" style="font-size:10px;color:var(--border)">' + Math.floor(elapsed / 1000) + 's</span>' +
|
|
50
|
+
' <button onclick="ccNewSession()" style="font-size:9px;padding:2px 8px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;color:var(--red);cursor:pointer">Reset</button>';
|
|
51
|
+
el.appendChild(thinking);
|
|
52
|
+
el.scrollTop = el.scrollHeight;
|
|
53
|
+
// Update timer
|
|
54
|
+
const startTime = sendingState.startedAt;
|
|
55
|
+
const restoreTimer = setInterval(function() {
|
|
56
|
+
var timeEl = document.getElementById('cc-thinking-time');
|
|
57
|
+
if (!timeEl || !_ccSending) { clearInterval(restoreTimer); return; }
|
|
58
|
+
timeEl.textContent = Math.floor((Date.now() - startTime) / 1000) + 's';
|
|
59
|
+
}, 1000);
|
|
60
|
+
}
|
|
61
|
+
} catch {}
|
|
40
62
|
}
|
|
41
63
|
|
|
42
64
|
function ccSaveState() {
|
|
@@ -109,6 +131,7 @@ async function ccSend() {
|
|
|
109
131
|
|
|
110
132
|
async function _ccDoSend(message, skipUserMsg) {
|
|
111
133
|
_ccSending = true;
|
|
134
|
+
try { localStorage.setItem('cc-sending', JSON.stringify({ sending: true, startedAt: Date.now() })); } catch {}
|
|
112
135
|
|
|
113
136
|
if (!skipUserMsg) ccAddMessage('user', escHtml(message));
|
|
114
137
|
|
|
@@ -160,7 +183,9 @@ async function _ccDoSend(message, skipUserMsg) {
|
|
|
160
183
|
thinking.remove();
|
|
161
184
|
|
|
162
185
|
if (data.error) {
|
|
163
|
-
|
|
186
|
+
const isBusy = data.error.includes('busy');
|
|
187
|
+
ccAddMessage('assistant', '<span style="color:var(--red)">' + escHtml(data.error) + '</span>' +
|
|
188
|
+
(isBusy ? ' <button onclick="ccNewSession()" style="margin-top:4px;padding:3px 10px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;color:var(--blue);cursor:pointer;font-size:10px">Reset CC</button>' : ''));
|
|
164
189
|
return;
|
|
165
190
|
}
|
|
166
191
|
|
|
@@ -201,6 +226,7 @@ async function _ccDoSend(message, skipUserMsg) {
|
|
|
201
226
|
'<button id="' + retryId + '" onclick="ccRetryLast()" style="margin-top:6px;padding:4px 12px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;color:var(--blue);cursor:pointer;font-size:11px">Retry</button>');
|
|
202
227
|
} finally {
|
|
203
228
|
_ccSending = false;
|
|
229
|
+
try { localStorage.removeItem('cc-sending'); } catch {}
|
|
204
230
|
// Show notification badge on CC button if drawer is closed
|
|
205
231
|
if (!_ccOpen) showNotifBadge(document.getElementById('cc-toggle-btn'));
|
|
206
232
|
}
|
|
@@ -173,11 +173,11 @@ function _renderMeetingDetail(m) {
|
|
|
173
173
|
} else {
|
|
174
174
|
html += '<div style="display:flex;gap:8px;border-top:1px solid var(--border);padding-top:8px">' +
|
|
175
175
|
'<input id="meeting-note-input" type="text" placeholder="Add context for all agents..." style="flex:1;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:12px" onkeydown="if(event.key===\'Enter\')_submitMeetingNote(\'' + escHtml(m.id) + '\')">' +
|
|
176
|
-
'<button onclick="_submitMeetingNote(\'' + escHtml(m.id) + '\')" style="padding:6px 12px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer;font-size:11px">Add Note</button>' +
|
|
176
|
+
'<button onclick="_submitMeetingNote(\'' + escHtml(m.id) + '\',this)" style="padding:6px 12px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer;font-size:11px">Add Note</button>' +
|
|
177
177
|
'</div>' +
|
|
178
178
|
'<div style="display:flex;gap:8px;margin-top:4px">' +
|
|
179
|
-
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--yellow);border-color:var(--yellow)" onclick="_advanceMeeting(\'' + escHtml(m.id) + '\')">Skip to Next Round</button>' +
|
|
180
|
-
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red);border-color:var(--red)" onclick="_endMeeting(\'' + escHtml(m.id) + '\')">End Meeting</button>' +
|
|
179
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--yellow);border-color:var(--yellow)" onclick="_advanceMeeting(\'' + escHtml(m.id) + '\',this)">Skip to Next Round</button>' +
|
|
180
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red);border-color:var(--red)" onclick="_endMeeting(\'' + escHtml(m.id) + '\',this)">End Meeting</button>' +
|
|
181
181
|
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red);border-color:var(--red)" onclick="_deleteMeeting(\'' + escHtml(m.id) + '\')">Delete</button>' +
|
|
182
182
|
'</div>';
|
|
183
183
|
}
|
|
@@ -269,45 +269,58 @@ async function _submitCreateMeeting() {
|
|
|
269
269
|
} catch (e) { alert('Error: ' + e.message); openCreateMeetingModal(); }
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
async function _submitMeetingNote(id) {
|
|
272
|
+
async function _submitMeetingNote(id, btn) {
|
|
273
273
|
const input = document.getElementById('meeting-note-input');
|
|
274
274
|
if (!input?.value?.trim()) return;
|
|
275
275
|
const note = input.value.trim();
|
|
276
276
|
input.value = '';
|
|
277
|
+
if (btn) { btn.textContent = 'Adding...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
|
|
277
278
|
try {
|
|
278
279
|
const res = await fetch('/api/meetings/note', {
|
|
279
280
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
280
281
|
body: JSON.stringify({ id, note })
|
|
281
282
|
});
|
|
282
|
-
if (res.ok)
|
|
283
|
+
if (res.ok) { showToast('cmd-toast', 'Note added', true); }
|
|
283
284
|
else { input.value = note; alert('Failed to add note'); }
|
|
284
285
|
} catch (e) { input.value = note; alert('Error: ' + e.message); }
|
|
286
|
+
if (btn) { btn.textContent = 'Add Note'; btn.style.pointerEvents = ''; btn.style.opacity = ''; }
|
|
285
287
|
}
|
|
286
288
|
|
|
287
|
-
async function _advanceMeeting(id) {
|
|
289
|
+
async function _advanceMeeting(id, btn) {
|
|
288
290
|
if (!confirm('Skip to next round? Agents that haven\'t finished will be skipped.')) return;
|
|
289
|
-
|
|
291
|
+
if (btn) { btn.textContent = 'Advancing...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
|
|
290
292
|
try {
|
|
291
|
-
await fetch('/api/meetings/advance', {
|
|
293
|
+
const res = await fetch('/api/meetings/advance', {
|
|
292
294
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
293
295
|
body: JSON.stringify({ id })
|
|
294
296
|
});
|
|
295
|
-
|
|
296
|
-
|
|
297
|
+
if (res.ok) {
|
|
298
|
+
showToast('cmd-toast', 'Advanced to next round', true);
|
|
299
|
+
wakeEngine();
|
|
300
|
+
} else {
|
|
301
|
+
const d = await res.json().catch(function() { return {}; });
|
|
302
|
+
alert('Advance failed: ' + (d.error || 'unknown'));
|
|
303
|
+
}
|
|
297
304
|
} catch (e) { alert('Error: ' + e.message); }
|
|
305
|
+
if (btn) { btn.textContent = 'Skip to Next Round'; btn.style.pointerEvents = ''; btn.style.opacity = ''; }
|
|
298
306
|
}
|
|
299
307
|
|
|
300
|
-
async function _endMeeting(id) {
|
|
308
|
+
async function _endMeeting(id, btn) {
|
|
301
309
|
if (!confirm('End this meeting? Current round will be stopped.')) return;
|
|
302
|
-
|
|
303
|
-
showToast('cmd-toast', 'Meeting ended', true);
|
|
310
|
+
if (btn) { btn.textContent = 'Ending...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
|
|
304
311
|
try {
|
|
305
|
-
await fetch('/api/meetings/end', {
|
|
312
|
+
const res = await fetch('/api/meetings/end', {
|
|
306
313
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
307
314
|
body: JSON.stringify({ id })
|
|
308
315
|
});
|
|
309
|
-
|
|
310
|
-
|
|
316
|
+
if (res.ok) {
|
|
317
|
+
showToast('cmd-toast', 'Meeting ended', true);
|
|
318
|
+
} else {
|
|
319
|
+
const d = await res.json().catch(function() { return {}; });
|
|
320
|
+
alert('End failed: ' + (d.error || 'unknown'));
|
|
321
|
+
if (btn) { btn.textContent = 'End Meeting'; btn.style.pointerEvents = ''; btn.style.opacity = ''; }
|
|
322
|
+
}
|
|
323
|
+
} catch (e) { alert('Error: ' + e.message); if (btn) { btn.textContent = 'End Meeting'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
|
|
311
324
|
}
|
|
312
325
|
|
|
313
326
|
async function _archiveMeeting(id) {
|
|
@@ -172,19 +172,15 @@ function renderPlans(plans) {
|
|
|
172
172
|
const isArchived = p.archived;
|
|
173
173
|
|
|
174
174
|
// For .md plans with a linked PRD, use the PRD's status as the authoritative intent
|
|
175
|
-
// (p.status for .md is 'draft'/'converted'/'active', not the PRD lifecycle status)
|
|
176
175
|
let prdJsonStatus = p.status || 'active';
|
|
177
176
|
if (prdFile && p.format !== 'prd') {
|
|
178
177
|
const linkedPrd = plans.find(pp => pp.file === prdFile && pp.format === 'prd');
|
|
179
178
|
if (linkedPrd) prdJsonStatus = linkedPrd.status || prdJsonStatus;
|
|
180
|
-
// If the linked PRD was archived, treat the .md plan as completed
|
|
181
179
|
else if (!linkedPrd) {
|
|
182
180
|
const archivedPrd = archivedPlans.find(pp => pp.file === prdFile && pp.format === 'prd');
|
|
183
181
|
if (archivedPrd) prdJsonStatus = 'completed';
|
|
184
182
|
}
|
|
185
183
|
}
|
|
186
|
-
// 'converted' means plan-to-PRD succeeded — treat as 'approved' for status derivation
|
|
187
|
-
if (prdJsonStatus === 'converted') prdJsonStatus = prdFile ? 'approved' : 'completed';
|
|
188
184
|
|
|
189
185
|
// Single source of truth: derive status from work items
|
|
190
186
|
const effectiveStatus = isArchived ? 'completed' : derivePlanStatus(prdFile, p.file, prdJsonStatus, allWi);
|
|
@@ -192,14 +188,13 @@ function renderPlans(plans) {
|
|
|
192
188
|
const statusLabelsMap = {
|
|
193
189
|
'completed': 'Completed', 'in-progress': 'In Progress', 'paused': 'Paused',
|
|
194
190
|
'awaiting-approval': 'Awaiting Approval', 'approved': 'Approved', 'rejected': 'Rejected',
|
|
195
|
-
'revision-requested': 'Revision Requested', 'has-failures': 'Has Failures', 'active': 'Active'
|
|
196
|
-
'converted': 'Converted to PRD', 'draft': 'Draft'
|
|
191
|
+
'revision-requested': 'Revision Requested', 'has-failures': 'Has Failures', 'active': 'Active'
|
|
197
192
|
};
|
|
198
193
|
const label = statusLabelsMap[effectiveStatus] || effectiveStatus;
|
|
199
194
|
const needsAction = (effectiveStatus === 'awaiting-approval' || effectiveStatus === 'paused') && !isArchived;
|
|
200
195
|
const isRevision = effectiveStatus === 'revision-requested';
|
|
201
196
|
const isCompleted = effectiveStatus === 'completed';
|
|
202
|
-
const isDraft =
|
|
197
|
+
const isDraft = p.format === 'draft' && !isCompleted;
|
|
203
198
|
// For .md drafts: show Execute only if no PRD exists yet (not already executed)
|
|
204
199
|
|
|
205
200
|
let actions = '';
|
|
@@ -571,7 +566,10 @@ async function planArchive(file, btn) {
|
|
|
571
566
|
const d = await res.json().catch(() => ({}));
|
|
572
567
|
if (res.ok && d.ok) {
|
|
573
568
|
try { closeModal(); } catch { /* may not be open */ }
|
|
574
|
-
|
|
569
|
+
var msg = 'Archived';
|
|
570
|
+
if (d.archivedSource) msg += ' PRD + source plan (' + d.archivedSource + ')';
|
|
571
|
+
if (d.cancelledItems) msg += ', cancelled ' + d.cancelledItems + ' pending item(s)';
|
|
572
|
+
showToast('cmd-toast', msg, true);
|
|
575
573
|
refreshPlans();
|
|
576
574
|
refresh();
|
|
577
575
|
} else {
|
package/engine/lifecycle.js
CHANGED
|
@@ -1221,24 +1221,24 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1221
1221
|
|
|
1222
1222
|
if (isSuccess && meta?.item?.id && !skipDoneStatus) updateWorkItemStatus(meta, 'done', '');
|
|
1223
1223
|
|
|
1224
|
-
// Auto-dispatch
|
|
1224
|
+
// Auto-dispatch review work item after implement completes successfully
|
|
1225
1225
|
if (isSuccess && !skipDoneStatus && type === 'implement' && meta?.item?.id) {
|
|
1226
|
-
const
|
|
1227
|
-
if (
|
|
1226
|
+
const autoReview = config.engine?.autoReview ?? shared.ENGINE_DEFAULTS.autoReview;
|
|
1227
|
+
if (autoReview) {
|
|
1228
1228
|
try {
|
|
1229
1229
|
const wiPath = resolveWiPath(meta);
|
|
1230
1230
|
if (wiPath) {
|
|
1231
1231
|
const items = safeJson(wiPath) || [];
|
|
1232
|
-
// Dedup: skip if
|
|
1233
|
-
const existing = items.find(i => i._evalParentId === meta.item.id && i.type === '
|
|
1232
|
+
// Dedup: skip if a review item already exists for this parent
|
|
1233
|
+
const existing = items.find(i => i._evalParentId === meta.item.id && i.type === 'review');
|
|
1234
1234
|
if (existing) {
|
|
1235
|
-
log('info', `Eval loop:
|
|
1235
|
+
log('info', `Eval loop: review item ${existing.id} already exists for ${meta.item.id}, skipping`);
|
|
1236
1236
|
} else {
|
|
1237
1237
|
const parentItem = items.find(i => i.id === meta.item.id);
|
|
1238
1238
|
const evalItem = {
|
|
1239
1239
|
id: 'W-' + shared.uid(),
|
|
1240
|
-
title: `
|
|
1241
|
-
type: '
|
|
1240
|
+
title: `Review: ${meta.item.title || meta.item.id}`,
|
|
1241
|
+
type: 'review',
|
|
1242
1242
|
priority: meta.item.priority || 'high',
|
|
1243
1243
|
status: 'pending',
|
|
1244
1244
|
created: ts(),
|
|
@@ -1263,14 +1263,14 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1263
1263
|
}
|
|
1264
1264
|
}
|
|
1265
1265
|
|
|
1266
|
-
//
|
|
1267
|
-
if (isSuccess && type === '
|
|
1266
|
+
// Review completion: parse verdict and handle review→fix iteration loop
|
|
1267
|
+
if (isSuccess && type === 'review' && meta?.item?._evalParentId) {
|
|
1268
1268
|
try {
|
|
1269
1269
|
const verdict = parseEvalVerdict(resultSummary || stdout);
|
|
1270
|
-
const
|
|
1270
|
+
const autoReview = config.engine?.autoReview ?? shared.ENGINE_DEFAULTS.autoReview;
|
|
1271
1271
|
const maxIter = config.engine?.evalMaxIterations ?? shared.ENGINE_DEFAULTS.evalMaxIterations;
|
|
1272
1272
|
|
|
1273
|
-
if (verdict && !verdict.pass &&
|
|
1273
|
+
if (verdict && !verdict.pass && autoReview) {
|
|
1274
1274
|
const wiPath = resolveWiPath(meta);
|
|
1275
1275
|
if (wiPath) {
|
|
1276
1276
|
const items = safeJson(wiPath) || [];
|
package/engine.js
CHANGED
|
@@ -1256,8 +1256,8 @@ function discoverFromPrs(config, project) {
|
|
|
1256
1256
|
if (!agentId) continue;
|
|
1257
1257
|
|
|
1258
1258
|
const item = buildPrDispatch(agentId, config, project, pr, 'review', {
|
|
1259
|
-
pr_id: pr.id, pr_number: prNumber, pr_title: pr.title
|
|
1260
|
-
pr_author: pr.agent || '', pr_url: pr.url || '',
|
|
1259
|
+
pr_id: pr.id, pr_number: prNumber, pr_title: pr.title ? ': ' + pr.title : '', pr_branch: pr.branch || '',
|
|
1260
|
+
branch_name: pr.branch || '', pr_author: pr.agent || '', pr_url: pr.url || '',
|
|
1261
1261
|
}, `Review PR ${pr.id}: ${pr.title}`, { dispatchKey: key, source: 'pr', pr, branch: pr.branch, project: projMeta });
|
|
1262
1262
|
if (item) { newWork.push(item); setCooldown(key); }
|
|
1263
1263
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.151",
|
|
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"
|
package/playbooks/work-item.md
CHANGED
|
@@ -40,7 +40,7 @@ Keep branch names lowercase, use hyphens, max 60 chars.
|
|
|
40
40
|
- title: `feat({{item_id}}): <description>`
|
|
41
41
|
8. **Post implementation notes** as a PR thread comment:
|
|
42
42
|
{{pr_comment_instructions}}
|
|
43
|
-
9. **Add PR to tracker** — append to `{{
|
|
43
|
+
9. **Add PR to tracker** — append to `{{team_root}}/projects/{{project_name}}/pull-requests.json`:
|
|
44
44
|
```json
|
|
45
45
|
{ "id": "PR-<number>", "title": "...", "agent": "{{agent_name}}", "branch": "...", "reviewStatus": "pending", "status": "active", "created": "<date>", "url": "<pr-url>", "prdItems": ["{{item_id}}"] }
|
|
46
46
|
```
|