@yemi33/minions 0.1.150 → 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 +13 -0
- package/dashboard/js/command-center.js +27 -1
- package/dashboard/js/render-meetings.js +29 -16
- 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,18 @@
|
|
|
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
|
+
|
|
3
16
|
## 0.1.150 (2026-04-01)
|
|
4
17
|
|
|
5
18
|
### Dashboard
|
|
@@ -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) {
|
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
|
```
|