@yemi33/minions 0.1.113 → 0.1.115
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 +22 -0
- package/dashboard/js/render-inbox.js +19 -17
- package/dashboard/js/render-kb.js +4 -3
- package/dashboard/js/render-meetings.js +54 -28
- package/dashboard/js/render-pinned.js +10 -6
- package/dashboard/js/render-prs.js +4 -8
- package/dashboard/js/render-schedules.js +10 -7
- package/dashboard/js/render-work-items.js +22 -20
- package/dashboard/js/settings.js +16 -0
- package/dashboard/js/utils.js +5 -0
- package/dashboard.js +3 -0
- package/engine/shared.js +1 -0
- package/engine.js +13 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.115 (2026-04-01)
|
|
4
|
+
|
|
5
|
+
### Engine
|
|
6
|
+
- engine.js
|
|
7
|
+
- engine/shared.js
|
|
8
|
+
|
|
9
|
+
### Dashboard
|
|
10
|
+
- dashboard.js
|
|
11
|
+
- dashboard/js/settings.js
|
|
12
|
+
|
|
13
|
+
## 0.1.114 (2026-04-01)
|
|
14
|
+
|
|
15
|
+
### Dashboard
|
|
16
|
+
- dashboard/js/render-inbox.js
|
|
17
|
+
- dashboard/js/render-kb.js
|
|
18
|
+
- dashboard/js/render-meetings.js
|
|
19
|
+
- dashboard/js/render-pinned.js
|
|
20
|
+
- dashboard/js/render-prs.js
|
|
21
|
+
- dashboard/js/render-schedules.js
|
|
22
|
+
- dashboard/js/render-work-items.js
|
|
23
|
+
- dashboard/js/utils.js
|
|
24
|
+
|
|
3
25
|
## 0.1.113 (2026-04-01)
|
|
4
26
|
|
|
5
27
|
### Dashboard
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// render-inbox.js — Inbox and notes rendering functions extracted from dashboard.html
|
|
2
2
|
|
|
3
3
|
function renderInbox(inbox) {
|
|
4
|
+
inbox = inbox.filter(function(item) { return !isDeleted('inbox:' + item.name); });
|
|
4
5
|
inboxData = inbox;
|
|
5
6
|
const list = document.getElementById('inbox-list');
|
|
6
7
|
const count = document.getElementById('inbox-count');
|
|
@@ -128,13 +129,16 @@ function modalCancelEdit() {
|
|
|
128
129
|
|
|
129
130
|
async function deleteInboxItem(name) {
|
|
130
131
|
if (!confirm('Delete "' + name + '" from inbox?')) return;
|
|
132
|
+
markDeleted('inbox:' + name);
|
|
133
|
+
const card = document.querySelector('.inbox-item[data-file="notes/inbox/' + CSS.escape(name) + '"]');
|
|
134
|
+
if (card) card.remove();
|
|
131
135
|
try {
|
|
132
136
|
const res = await fetch('/api/inbox/delete', {
|
|
133
137
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
134
138
|
body: JSON.stringify({ name })
|
|
135
139
|
});
|
|
136
|
-
if (res.ok) {
|
|
137
|
-
} catch (e) { alert('
|
|
140
|
+
if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Delete failed: ' + (d.error || 'unknown')); refresh(); }
|
|
141
|
+
} catch (e) { alert('Delete error: ' + e.message); refresh(); }
|
|
138
142
|
}
|
|
139
143
|
|
|
140
144
|
async function openInboxInExplorer(name) {
|
|
@@ -169,35 +173,33 @@ async function submitQuickNote() {
|
|
|
169
173
|
const title = titleEl.value;
|
|
170
174
|
const content = contentEl.value;
|
|
171
175
|
if (!title && !content) { alert('Title or content required'); return; }
|
|
176
|
+
try { closeModal(); } catch { /* expected */ }
|
|
177
|
+
showToast('cmd-toast', 'Note saved to inbox', true);
|
|
172
178
|
try {
|
|
173
179
|
const res = await fetch('/api/notes', {
|
|
174
180
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
175
181
|
body: JSON.stringify({ title: title || 'Quick note', what: content || title })
|
|
176
182
|
});
|
|
177
|
-
if (res.ok) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
try { showToast('cmd-toast', 'Note saved to inbox', true); } catch { /* expected */ }
|
|
181
|
-
}
|
|
182
|
-
else { const d = await res.json().catch(() => ({})); alert('Error: ' + (d.error || 'unknown')); }
|
|
183
|
-
} catch (e) { alert('Error saving note: ' + e.message); }
|
|
183
|
+
if (res.ok) { refresh(); }
|
|
184
|
+
else { const d = await res.json().catch(() => ({})); alert('Note failed: ' + (d.error || 'unknown')); openQuickNoteModal(); }
|
|
185
|
+
} catch (e) { alert('Error saving note: ' + e.message); openQuickNoteModal(); }
|
|
184
186
|
}
|
|
185
187
|
|
|
186
188
|
async function doPromoteToKB(name, category) {
|
|
189
|
+
try { closeModal(); } catch { /* expected */ }
|
|
190
|
+
markDeleted('inbox:' + name);
|
|
191
|
+
const card = document.querySelector('.inbox-item[data-file="notes/inbox/' + CSS.escape(name) + '"]');
|
|
192
|
+
if (card) card.remove();
|
|
193
|
+
showToast('cmd-toast', 'Promoted to Knowledge Base', true);
|
|
187
194
|
try {
|
|
188
195
|
const res = await fetch('/api/inbox/promote-kb', {
|
|
189
196
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
190
197
|
body: JSON.stringify({ name, category })
|
|
191
198
|
});
|
|
192
199
|
const data = await res.json();
|
|
193
|
-
if (res.ok) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
refreshKnowledgeBase();
|
|
197
|
-
} else {
|
|
198
|
-
alert('Failed: ' + (data.error || 'unknown'));
|
|
199
|
-
}
|
|
200
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
200
|
+
if (res.ok) { refreshKnowledgeBase(); }
|
|
201
|
+
else { alert('Failed: ' + (data.error || 'unknown')); refresh(); }
|
|
202
|
+
} catch (e) { alert('Error: ' + e.message); refresh(); }
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
window.MinionsInbox = { renderInbox, promoteToKB, renderNotes, openNotesModal, modalToggleEdit, modalSaveEdit, modalCancelEdit, deleteInboxItem, openInboxInExplorer, openQuickNoteModal, submitQuickNote, doPromoteToKB };
|
|
@@ -147,14 +147,15 @@ async function submitKbEntry() {
|
|
|
147
147
|
const title = document.getElementById('kb-new-title').value;
|
|
148
148
|
const content = document.getElementById('kb-new-content').value;
|
|
149
149
|
if (!title || !content) { alert('Title and content are required'); return; }
|
|
150
|
+
try { closeModal(); } catch { /* may not be open */ }
|
|
151
|
+
showToast('cmd-toast', 'KB entry created', true);
|
|
150
152
|
try {
|
|
151
153
|
const res = await fetch('/api/knowledge', {
|
|
152
154
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
153
155
|
body: JSON.stringify({ category, title, content })
|
|
154
156
|
});
|
|
155
|
-
if (res.ok) {
|
|
156
|
-
|
|
157
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
157
|
+
if (res.ok) { refreshKnowledgeBase(); } else { const d = await res.json().catch(() => ({})); alert('KB create failed: ' + (d.error || 'unknown')); openNewKbModal(); }
|
|
158
|
+
} catch (e) { alert('Error: ' + e.message); openNewKbModal(); }
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
async function kbOpenItem(category, file) {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// render-meetings.js — Team meeting rendering
|
|
2
2
|
|
|
3
3
|
let _showArchived = false;
|
|
4
|
+
const MTG_PER_PAGE = 10;
|
|
5
|
+
let _mtgPage = 0;
|
|
4
6
|
|
|
5
7
|
function renderMeetings(meetings) {
|
|
8
|
+
meetings = (meetings || []).filter(function(m) { return !isDeleted('mtg:' + m.id); });
|
|
6
9
|
const el = document.getElementById('meetings-content');
|
|
7
10
|
const countEl = document.getElementById('meetings-count');
|
|
8
11
|
if (!meetings || meetings.length === 0) {
|
|
@@ -25,7 +28,12 @@ function renderMeetings(meetings) {
|
|
|
25
28
|
return;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
const totalPages = Math.ceil(visible.length / MTG_PER_PAGE);
|
|
32
|
+
if (_mtgPage >= totalPages) _mtgPage = totalPages - 1;
|
|
33
|
+
const start = _mtgPage * MTG_PER_PAGE;
|
|
34
|
+
const pageItems = visible.slice(start, start + MTG_PER_PAGE);
|
|
35
|
+
|
|
36
|
+
el.innerHTML = pageItems.map(m => {
|
|
29
37
|
const statusColor = statusColors[m.status] || 'var(--muted)';
|
|
30
38
|
const statusLabel = statusLabels[m.status] || m.status;
|
|
31
39
|
const participantBadges = (m.participants || []).map(p => {
|
|
@@ -55,14 +63,27 @@ function renderMeetings(meetings) {
|
|
|
55
63
|
'</div>';
|
|
56
64
|
}).join('');
|
|
57
65
|
|
|
66
|
+
if (visible.length > MTG_PER_PAGE) {
|
|
67
|
+
el.innerHTML += '<div class="pr-pager">' +
|
|
68
|
+
'<span class="pr-page-info">Showing ' + (start + 1) + ' to ' + Math.min(start + MTG_PER_PAGE, visible.length) + ' of ' + visible.length + '</span>' +
|
|
69
|
+
'<div class="pr-pager-btns">' +
|
|
70
|
+
'<button class="pr-pager-btn ' + (_mtgPage === 0 ? 'disabled' : '') + '" onclick="_mtgPrev()">Prev</button>' +
|
|
71
|
+
'<button class="pr-pager-btn ' + (_mtgPage >= totalPages - 1 ? 'disabled' : '') + '" onclick="_mtgNext()">Next</button>' +
|
|
72
|
+
'</div></div>';
|
|
73
|
+
}
|
|
74
|
+
|
|
58
75
|
if (archived.length > 0) {
|
|
59
76
|
el.innerHTML += '<div style="text-align:center;margin-top:8px"><button class="pr-pager-btn" style="font-size:10px" onclick="_toggleArchivedMeetings()">' +
|
|
60
77
|
(_showArchived ? 'Hide' : 'Show') + ' ' + archived.length + ' archived</button></div>';
|
|
61
78
|
}
|
|
62
79
|
}
|
|
63
80
|
|
|
81
|
+
function _mtgPrev() { if (_mtgPage > 0) { _mtgPage--; refresh(); } }
|
|
82
|
+
function _mtgNext() { _mtgPage++; refresh(); }
|
|
83
|
+
|
|
64
84
|
function _toggleArchivedMeetings() {
|
|
65
85
|
_showArchived = !_showArchived;
|
|
86
|
+
_mtgPage = 0;
|
|
66
87
|
refresh();
|
|
67
88
|
}
|
|
68
89
|
|
|
@@ -207,37 +228,37 @@ async function _submitCreateMeeting() {
|
|
|
207
228
|
const checks = document.querySelectorAll('#mtg-participants input[type="checkbox"]:checked');
|
|
208
229
|
const participants = [...checks].map(c => c.value);
|
|
209
230
|
if (participants.length < 2) { alert('Select at least 2 participants'); return; }
|
|
210
|
-
|
|
231
|
+
try { closeModal(); } catch { /* expected */ }
|
|
232
|
+
showToast('cmd-toast', 'Meeting started with ' + participants.length + ' agents', true);
|
|
211
233
|
try {
|
|
212
234
|
const res = await fetch('/api/meetings', {
|
|
213
235
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
214
236
|
body: JSON.stringify({ title, agenda, participants })
|
|
215
237
|
});
|
|
216
238
|
const data = await res.json();
|
|
217
|
-
if (res.ok) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
refresh();
|
|
221
|
-
try { showToast('cmd-toast', 'Meeting started with ' + participants.length + ' agents', true); } catch { /* expected */ }
|
|
222
|
-
} else { alert('Failed: ' + (data.error || 'unknown')); }
|
|
223
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
239
|
+
if (res.ok) { wakeEngine(); refresh(); }
|
|
240
|
+
else { alert('Failed: ' + (data.error || 'unknown')); openCreateMeetingModal(); }
|
|
241
|
+
} catch (e) { alert('Error: ' + e.message); openCreateMeetingModal(); }
|
|
224
242
|
}
|
|
225
243
|
|
|
226
244
|
async function _submitMeetingNote(id) {
|
|
227
245
|
const input = document.getElementById('meeting-note-input');
|
|
228
246
|
if (!input?.value?.trim()) return;
|
|
247
|
+
const note = input.value.trim();
|
|
248
|
+
input.value = '';
|
|
229
249
|
try {
|
|
230
|
-
await fetch('/api/meetings/note', {
|
|
250
|
+
const res = await fetch('/api/meetings/note', {
|
|
231
251
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
232
|
-
body: JSON.stringify({ id, note
|
|
252
|
+
body: JSON.stringify({ id, note })
|
|
233
253
|
});
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
254
|
+
if (res.ok) openMeetingDetail(id);
|
|
255
|
+
else { input.value = note; alert('Failed to add note'); }
|
|
256
|
+
} catch (e) { input.value = note; alert('Error: ' + e.message); }
|
|
237
257
|
}
|
|
238
258
|
|
|
239
259
|
async function _advanceMeeting(id) {
|
|
240
260
|
if (!confirm('Skip to next round? Agents that haven\'t finished will be skipped.')) return;
|
|
261
|
+
showToast('cmd-toast', 'Advancing to next round...', true);
|
|
241
262
|
try {
|
|
242
263
|
await fetch('/api/meetings/advance', {
|
|
243
264
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
@@ -250,38 +271,42 @@ async function _advanceMeeting(id) {
|
|
|
250
271
|
|
|
251
272
|
async function _endMeeting(id) {
|
|
252
273
|
if (!confirm('End this meeting? Current round will be stopped.')) return;
|
|
274
|
+
try { closeModal(); } catch { /* expected */ }
|
|
275
|
+
showToast('cmd-toast', 'Meeting ended', true);
|
|
253
276
|
try {
|
|
254
277
|
await fetch('/api/meetings/end', {
|
|
255
278
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
256
279
|
body: JSON.stringify({ id })
|
|
257
280
|
});
|
|
258
|
-
try { closeModal(); } catch { /* expected */ }
|
|
259
281
|
refresh();
|
|
260
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
282
|
+
} catch (e) { alert('Error: ' + e.message); refresh(); }
|
|
261
283
|
}
|
|
262
284
|
|
|
263
285
|
async function _archiveMeeting(id) {
|
|
286
|
+
markDeleted('mtg:' + id);
|
|
287
|
+
try { closeModal(); } catch { /* may not be open */ }
|
|
288
|
+
document.querySelectorAll('[onclick*="openMeetingDetail(\'' + id + '\')"]').forEach(function(el) { el.remove(); });
|
|
289
|
+
showToast('cmd-toast', 'Meeting archived', true);
|
|
264
290
|
try {
|
|
265
291
|
const res = await fetch('/api/meetings/archive', {
|
|
266
292
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
267
293
|
body: JSON.stringify({ id })
|
|
268
294
|
});
|
|
269
|
-
if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Failed: ' + (d.error || 'unknown'));
|
|
270
|
-
|
|
271
|
-
refresh();
|
|
272
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
295
|
+
if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Failed: ' + (d.error || 'unknown')); refresh(); }
|
|
296
|
+
} catch (e) { alert('Error: ' + e.message); refresh(); }
|
|
273
297
|
}
|
|
274
298
|
|
|
275
299
|
async function _unarchiveMeeting(id) {
|
|
300
|
+
try { closeModal(); } catch { /* may not be open */ }
|
|
301
|
+
document.querySelectorAll('[onclick*="openMeetingDetail(\'' + id + '\')"]').forEach(function(el) { el.remove(); });
|
|
302
|
+
showToast('cmd-toast', 'Meeting unarchived', true);
|
|
276
303
|
try {
|
|
277
304
|
const res = await fetch('/api/meetings/unarchive', {
|
|
278
305
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
279
306
|
body: JSON.stringify({ id })
|
|
280
307
|
});
|
|
281
|
-
if (!res.ok) { const d = await res.json().catch(() => ({})); alert('
|
|
282
|
-
|
|
283
|
-
refresh();
|
|
284
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
308
|
+
if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Delete failed: ' + (d.error || 'unknown')); refresh(); }
|
|
309
|
+
} catch (e) { alert('Error: ' + e.message); refresh(); }
|
|
285
310
|
}
|
|
286
311
|
|
|
287
312
|
function _viewPlanWithBack(file, meetingId) {
|
|
@@ -382,15 +407,16 @@ async function _createPlanFromMeeting(id, btn) {
|
|
|
382
407
|
|
|
383
408
|
async function _deleteMeeting(id) {
|
|
384
409
|
if (!confirm('Delete this meeting? This cannot be undone.')) return;
|
|
410
|
+
markDeleted('mtg:' + id);
|
|
411
|
+
try { closeModal(); } catch { /* may not be open */ }
|
|
412
|
+
document.querySelectorAll('[onclick*="openMeetingDetail(\'' + id + '\')"]').forEach(function(el) { el.remove(); });
|
|
385
413
|
try {
|
|
386
414
|
const res = await fetch('/api/meetings/delete', {
|
|
387
415
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
388
416
|
body: JSON.stringify({ id })
|
|
389
417
|
});
|
|
390
|
-
if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Failed: ' + (d.error || 'unknown'));
|
|
391
|
-
|
|
392
|
-
refresh();
|
|
393
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
418
|
+
if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Failed: ' + (d.error || 'unknown')); refresh(); }
|
|
419
|
+
} catch (e) { alert('Error: ' + e.message); refresh(); }
|
|
394
420
|
}
|
|
395
421
|
|
|
396
422
|
window.MinionsMeetings = { renderMeetings, openMeetingDetail, openCreateMeetingModal };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// dashboard/js/render-pinned.js — Pinned context notes rendering and management
|
|
2
2
|
|
|
3
3
|
function renderPinned(entries) {
|
|
4
|
+
entries = (entries || []).filter(function(e) { return !isDeleted('pin:' + e.title); });
|
|
4
5
|
const el = document.getElementById('pinned-content');
|
|
5
6
|
if (!el) return;
|
|
6
7
|
if (!entries || entries.length === 0) {
|
|
@@ -37,19 +38,22 @@ async function submitPinnedNote() {
|
|
|
37
38
|
const content = document.getElementById('pin-content').value;
|
|
38
39
|
const level = document.getElementById('pin-level').value;
|
|
39
40
|
if (!title || !content) { alert('Title and content required'); return; }
|
|
41
|
+
try { closeModal(); } catch { /* may not be open */ }
|
|
42
|
+
showToast('cmd-toast', 'Note pinned', true);
|
|
40
43
|
try {
|
|
41
44
|
const res = await fetch('/api/pinned', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title, content, level }) });
|
|
42
|
-
if (res.ok) {
|
|
43
|
-
|
|
44
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
45
|
+
if (res.ok) { refresh(); } else { const d = await res.json().catch(() => ({})); alert('Pin failed: ' + (d.error || 'unknown')); openPinNoteModal(); }
|
|
46
|
+
} catch (e) { alert('Error: ' + e.message); openPinNoteModal(); }
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
async function removePinnedNote(title) {
|
|
48
50
|
if (!confirm('Unpin "' + title + '"?')) return;
|
|
51
|
+
markDeleted('pin:' + title);
|
|
52
|
+
const btn = event?.target; if (btn) { const card = btn.closest('.pinned-card') || btn.parentElement?.parentElement; if (card) card.remove(); }
|
|
49
53
|
try {
|
|
50
|
-
await fetch('/api/pinned/remove', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title }) });
|
|
51
|
-
refresh();
|
|
52
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
54
|
+
const res = await fetch('/api/pinned/remove', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title }) });
|
|
55
|
+
if (!res.ok) { alert('Unpin failed'); refresh(); }
|
|
56
|
+
} catch (e) { alert('Error: ' + e.message); refresh(); }
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
window.MinionsPinned = { renderPinned, openPinNoteModal, submitPinnedNote, removePinnedNote };
|
|
@@ -130,20 +130,16 @@ async function _submitLinkPr() {
|
|
|
130
130
|
const context = document.getElementById('pr-link-context')?.value || '';
|
|
131
131
|
const autoObserve = document.getElementById('pr-link-observe')?.checked || false;
|
|
132
132
|
|
|
133
|
+
try { closeModal(); } catch { /* expected */ }
|
|
134
|
+
showToast('cmd-toast', 'PR linked' + (autoObserve ? ' (auto-observe on)' : ''), true);
|
|
133
135
|
try {
|
|
134
136
|
const res = await fetch('/api/pull-requests/link', {
|
|
135
137
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
136
138
|
body: JSON.stringify({ url, title, project, context, autoObserve })
|
|
137
139
|
});
|
|
138
140
|
const data = await res.json();
|
|
139
|
-
if (res.ok) {
|
|
140
|
-
|
|
141
|
-
refresh();
|
|
142
|
-
try { showToast('cmd-toast', 'PR ' + (data.id || '') + ' linked' + (autoObserve ? ' (auto-observe on)' : ''), true); } catch { /* expected */ }
|
|
143
|
-
} else {
|
|
144
|
-
alert('Failed: ' + (data.error || 'unknown'));
|
|
145
|
-
}
|
|
146
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
141
|
+
if (res.ok) { refresh(); } else { alert('Failed: ' + (data.error || 'unknown')); openAddPrModal(); }
|
|
142
|
+
} catch (e) { alert('Error: ' + e.message); openAddPrModal(); }
|
|
147
143
|
}
|
|
148
144
|
|
|
149
145
|
window.MinionsPrs = { prRow, prTableHtml, renderPrs, prPrev, prNext, openAllPrs, openModal, openAddPrModal };
|
|
@@ -181,6 +181,7 @@ let _schedPage = 0;
|
|
|
181
181
|
const SCHED_PER_PAGE = 15;
|
|
182
182
|
|
|
183
183
|
function renderSchedules(schedules) {
|
|
184
|
+
schedules = schedules.filter(function(s) { return !isDeleted('sched:' + s.id); });
|
|
184
185
|
const el = document.getElementById('scheduled-content');
|
|
185
186
|
const countEl = document.getElementById('scheduled-count');
|
|
186
187
|
countEl.textContent = schedules.length;
|
|
@@ -431,6 +432,8 @@ async function submitSchedule(isEdit) {
|
|
|
431
432
|
}
|
|
432
433
|
|
|
433
434
|
async function toggleScheduleEnabled(id, enabled) {
|
|
435
|
+
// Optimistic toggle — swap badge text immediately
|
|
436
|
+
document.querySelectorAll('tr').forEach(function(r) { if (r.textContent.includes(id)) { var badge = r.querySelector('.status-badge'); if (badge) badge.textContent = enabled ? 'ENABLED' : 'DISABLED'; } });
|
|
434
437
|
try {
|
|
435
438
|
const res = await fetch('/api/schedules/update', {
|
|
436
439
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
@@ -438,23 +441,23 @@ async function toggleScheduleEnabled(id, enabled) {
|
|
|
438
441
|
});
|
|
439
442
|
if (res.ok) { refresh(); } else {
|
|
440
443
|
const d = await res.json().catch(() => ({}));
|
|
441
|
-
_showScheduleError('Toggle failed: ' + (d.error || 'unknown'));
|
|
444
|
+
_showScheduleError('Toggle failed: ' + (d.error || 'unknown')); refresh();
|
|
442
445
|
}
|
|
443
|
-
} catch (e) { _showScheduleError('Toggle error: ' + e.message); }
|
|
446
|
+
} catch (e) { _showScheduleError('Toggle error: ' + e.message); refresh(); }
|
|
444
447
|
}
|
|
445
448
|
|
|
446
449
|
async function deleteSchedule(id) {
|
|
447
450
|
if (!confirm('Delete scheduled task "' + id + '"?')) return;
|
|
451
|
+
markDeleted('sched:' + id);
|
|
452
|
+
document.querySelectorAll('tr').forEach(function(r) { if (r.textContent.includes(id)) r.remove(); });
|
|
453
|
+
showToast('cmd-toast', 'Schedule deleted', true);
|
|
448
454
|
try {
|
|
449
455
|
const res = await fetch('/api/schedules/delete', {
|
|
450
456
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
451
457
|
body: JSON.stringify({ id })
|
|
452
458
|
});
|
|
453
|
-
if (res.ok) {
|
|
454
|
-
|
|
455
|
-
_showScheduleError('Delete failed: ' + (d.error || 'unknown'));
|
|
456
|
-
}
|
|
457
|
-
} catch (e) { _showScheduleError('Delete error: ' + e.message); }
|
|
459
|
+
if (!res.ok) { const d = await res.json().catch(() => ({})); _showScheduleError('Delete failed: ' + (d.error || 'unknown')); refresh(); }
|
|
460
|
+
} catch (e) { _showScheduleError('Delete error: ' + e.message); refresh(); }
|
|
458
461
|
}
|
|
459
462
|
|
|
460
463
|
// Expose _generateScheduleId globally for the inline oninput handler
|
|
@@ -47,6 +47,7 @@ function wiRow(item) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function renderWorkItems(items) {
|
|
50
|
+
items = items.filter(function(w) { return !isDeleted('wi:' + w.id); });
|
|
50
51
|
// Sort: active/dispatched first, then by most recent activity
|
|
51
52
|
const statusOrder = { dispatched: 0, pending: 1, queued: 1, failed: 2, done: 3 };
|
|
52
53
|
items.sort((a, b) => {
|
|
@@ -146,43 +147,41 @@ async function submitWorkItemEdit(id, source) {
|
|
|
146
147
|
const acRaw = document.getElementById('wi-edit-ac')?.value || '';
|
|
147
148
|
const acceptanceCriteria = acRaw.split('\n').filter(function(l) { return l.trim(); });
|
|
148
149
|
if (!title) { alert('Title is required'); return; }
|
|
150
|
+
try { closeModal(); } catch { /* may not be open */ }
|
|
151
|
+
showToast('cmd-toast', 'Work item updated', true);
|
|
149
152
|
try {
|
|
150
153
|
const res = await fetch('/api/work-items/update', {
|
|
151
154
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
152
155
|
body: JSON.stringify({ id, source: source || undefined, title, description, type, priority, agent, references, acceptanceCriteria })
|
|
153
156
|
});
|
|
154
|
-
if (res.ok) {
|
|
155
|
-
|
|
156
|
-
alert('Update failed: ' + (d.error || 'unknown'));
|
|
157
|
-
}
|
|
158
|
-
} catch (e) { alert('Update error: ' + e.message); }
|
|
157
|
+
if (res.ok) { refresh(); } else { const d = await res.json().catch(() => ({})); alert('Update failed: ' + (d.error || 'unknown')); editWorkItem(id, source); }
|
|
158
|
+
} catch (e) { alert('Update error: ' + e.message); editWorkItem(id, source); }
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
async function deleteWorkItem(id, source) {
|
|
162
162
|
if (!confirm('Delete work item ' + id + '? This will kill any running agent and remove all dispatch history.')) return;
|
|
163
|
+
markDeleted('wi:' + id);
|
|
164
|
+
document.querySelectorAll('tr').forEach(function(r) { if (r.textContent.includes(id)) r.remove(); });
|
|
163
165
|
try {
|
|
164
166
|
const res = await fetch('/api/work-items/delete', {
|
|
165
167
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
166
168
|
body: JSON.stringify({ id, source: source || undefined })
|
|
167
169
|
});
|
|
168
|
-
if (res.ok) { refresh(); }
|
|
169
|
-
|
|
170
|
-
alert('Delete failed: ' + (d.error || 'unknown'));
|
|
171
|
-
}
|
|
172
|
-
} catch (e) { alert('Delete error: ' + e.message); }
|
|
170
|
+
if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Delete failed: ' + (d.error || 'unknown')); refresh(); }
|
|
171
|
+
} catch (e) { alert('Delete error: ' + e.message); refresh(); }
|
|
173
172
|
}
|
|
174
173
|
|
|
175
174
|
async function archiveWorkItem(id, source) {
|
|
175
|
+
markDeleted('wi:' + id);
|
|
176
|
+
document.querySelectorAll('tr').forEach(function(r) { if (r.textContent.includes(id)) r.remove(); });
|
|
177
|
+
showToast('cmd-toast', 'Archived ' + id, true);
|
|
176
178
|
try {
|
|
177
179
|
const res = await fetch('/api/work-items/archive', {
|
|
178
180
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
179
181
|
body: JSON.stringify({ id, source: source || undefined })
|
|
180
182
|
});
|
|
181
|
-
if (res.ok) { refresh(); }
|
|
182
|
-
|
|
183
|
-
alert('Archive failed: ' + (d.error || 'unknown'));
|
|
184
|
-
}
|
|
185
|
-
} catch (e) { alert('Archive error: ' + e.message); }
|
|
183
|
+
if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Archive failed: ' + (d.error || 'unknown')); refresh(); return; }
|
|
184
|
+
} catch (e) { alert('Archive error: ' + e.message); refresh(); }
|
|
186
185
|
}
|
|
187
186
|
|
|
188
187
|
let wiArchiveVisible = false;
|
|
@@ -269,13 +268,14 @@ async function submitFeedback(id, source) {
|
|
|
269
268
|
const rating = _feedbackRating;
|
|
270
269
|
if (!rating) { alert('Please select a rating first'); return; }
|
|
271
270
|
const comment = document.getElementById('feedback-comment')?.value || '';
|
|
271
|
+
try { closeModal(); } catch { /* may not be open */ }
|
|
272
|
+
showToast('cmd-toast', 'Feedback saved — agents will learn from it', true);
|
|
272
273
|
try {
|
|
273
274
|
const res = await fetch('/api/work-items/feedback', {
|
|
274
275
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
275
276
|
body: JSON.stringify({ id, source, rating, comment })
|
|
276
277
|
});
|
|
277
|
-
if (res.ok) {
|
|
278
|
-
else { const d = await res.json(); alert('Error: ' + (d.error || 'unknown')); }
|
|
278
|
+
if (res.ok) { refresh(); } else { const d = await res.json().catch(() => ({})); alert('Feedback failed: ' + (d.error || 'unknown')); }
|
|
279
279
|
} catch (e) { alert('Error: ' + e.message); }
|
|
280
280
|
}
|
|
281
281
|
|
|
@@ -341,20 +341,22 @@ async function _submitCreateWorkItem() {
|
|
|
341
341
|
if (acceptanceCriteria.length) body.acceptanceCriteria = acceptanceCriteria;
|
|
342
342
|
if (references.length && references[0].url) body.references = references;
|
|
343
343
|
|
|
344
|
+
try { closeModal(); } catch { /* expected */ }
|
|
345
|
+
showToast('cmd-toast', 'Creating work item...', true);
|
|
344
346
|
const res = await fetch('/api/work-items', {
|
|
345
347
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
346
348
|
body: JSON.stringify(body)
|
|
347
349
|
});
|
|
348
350
|
const data = await res.json();
|
|
349
351
|
if (res.ok) {
|
|
350
|
-
try { closeModal(); } catch { /* expected */ }
|
|
351
352
|
wakeEngine();
|
|
352
353
|
refresh();
|
|
353
|
-
|
|
354
|
+
showToast('cmd-toast', 'Work item ' + (data.id || '') + ' created', true);
|
|
354
355
|
} else {
|
|
355
356
|
alert('Failed: ' + (data.error || 'unknown'));
|
|
357
|
+
openCreateWorkItemModal();
|
|
356
358
|
}
|
|
357
|
-
} catch (e) { alert('Error: ' + e.message); }
|
|
359
|
+
} catch (e) { alert('Error: ' + e.message); openCreateWorkItemModal(); }
|
|
358
360
|
}
|
|
359
361
|
|
|
360
362
|
function openWorkItemDetail(id) {
|
package/dashboard/js/settings.js
CHANGED
|
@@ -32,6 +32,11 @@ async function openSettings() {
|
|
|
32
32
|
settingsField('Worktree Create Timeout', 'set-worktreeCreateTimeout', e.worktreeCreateTimeout || 300000, 'ms', 'Timeout for git worktree add (increase for large repos/Windows)') +
|
|
33
33
|
settingsField('Worktree Create Retries', 'set-worktreeCreateRetries', e.worktreeCreateRetries || 1, '', 'Retry count for transient worktree add failures (0-3)') +
|
|
34
34
|
'</div>' +
|
|
35
|
+
'<div style="display:flex;flex-direction:column;gap:6px;margin-bottom:16px">' +
|
|
36
|
+
settingsToggle('Auto-approve Plans', 'set-autoApprovePlans', !!e.autoApprovePlans, 'PRDs are approved automatically without human review') +
|
|
37
|
+
settingsToggle('Auto-decompose', 'set-autoDecompose', e.autoDecompose !== false, 'Large implement items are auto-split into sub-tasks') +
|
|
38
|
+
settingsToggle('Allow Temp Agents', 'set-allowTempAgents', !!e.allowTempAgents, 'Spawn ephemeral agents when all permanent agents are busy') +
|
|
39
|
+
'</div>' +
|
|
35
40
|
|
|
36
41
|
'<h3 style="font-size:13px;color:var(--blue);margin-bottom:8px">Claude CLI</h3>' +
|
|
37
42
|
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px">' +
|
|
@@ -72,6 +77,14 @@ async function openSettings() {
|
|
|
72
77
|
document.getElementById('modal').classList.add('open');
|
|
73
78
|
}
|
|
74
79
|
|
|
80
|
+
function settingsToggle(label, id, checked, hint) {
|
|
81
|
+
return '<div style="display:flex;align-items:center;gap:8px;padding:4px 0">' +
|
|
82
|
+
'<input type="checkbox" id="' + id + '"' + (checked ? ' checked' : '') + ' style="accent-color:var(--blue);width:16px;height:16px;cursor:pointer">' +
|
|
83
|
+
'<label for="' + id + '" style="font-size:12px;color:var(--text);cursor:pointer">' + escHtml(label) + '</label>' +
|
|
84
|
+
(hint ? '<span style="font-size:9px;color:var(--muted)">' + escHtml(hint) + '</span>' : '') +
|
|
85
|
+
'</div>';
|
|
86
|
+
}
|
|
87
|
+
|
|
75
88
|
function settingsField(label, id, value, unit, hint) {
|
|
76
89
|
return '<div>' +
|
|
77
90
|
'<label style="font-size:10px;color:var(--muted);display:block;margin-bottom:2px">' + escHtml(label) + (unit ? ' <span style="opacity:0.6">(' + escHtml(unit) + ')</span>' : '') + '</label>' +
|
|
@@ -95,6 +108,9 @@ async function saveSettings() {
|
|
|
95
108
|
heartbeatTimeout: document.getElementById('set-heartbeatTimeout').value,
|
|
96
109
|
worktreeCreateTimeout: document.getElementById('set-worktreeCreateTimeout').value,
|
|
97
110
|
worktreeCreateRetries: document.getElementById('set-worktreeCreateRetries').value,
|
|
111
|
+
autoApprovePlans: document.getElementById('set-autoApprovePlans').checked,
|
|
112
|
+
autoDecompose: document.getElementById('set-autoDecompose').checked,
|
|
113
|
+
allowTempAgents: document.getElementById('set-allowTempAgents').checked,
|
|
98
114
|
};
|
|
99
115
|
|
|
100
116
|
const claudePayload = {
|
package/dashboard/js/utils.js
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
// Signal the engine to tick immediately (pick up new work without waiting 60s)
|
|
4
4
|
function wakeEngine() { fetch('/api/engine/wakeup', { method: 'POST' }).catch(() => {}); }
|
|
5
5
|
|
|
6
|
+
// Optimistic delete suppression — prevent auto-refresh from re-showing deleted items
|
|
7
|
+
const _deletedIds = new Map(); // key → expiry timestamp
|
|
8
|
+
function markDeleted(key) { _deletedIds.set(key, Date.now() + 10000); } // suppress for 10s
|
|
9
|
+
function isDeleted(key) { const exp = _deletedIds.get(key); if (!exp) return false; if (Date.now() > exp) { _deletedIds.delete(key); return false; } return true; }
|
|
10
|
+
|
|
6
11
|
function escHtml(s) {
|
|
7
12
|
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
|
|
8
13
|
}
|
package/dashboard.js
CHANGED
|
@@ -3065,6 +3065,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3065
3065
|
if (e.heartbeatTimeout !== undefined) config.engine.heartbeatTimeout = Math.max(60000, Number(e.heartbeatTimeout) || D.heartbeatTimeout);
|
|
3066
3066
|
if (e.worktreeCreateTimeout !== undefined) config.engine.worktreeCreateTimeout = Math.max(60000, Number(e.worktreeCreateTimeout) || D.worktreeCreateTimeout);
|
|
3067
3067
|
if (e.worktreeCreateRetries !== undefined) config.engine.worktreeCreateRetries = Math.max(0, Math.min(3, Number(e.worktreeCreateRetries) || D.worktreeCreateRetries));
|
|
3068
|
+
if (e.autoApprovePlans !== undefined) config.engine.autoApprovePlans = !!e.autoApprovePlans;
|
|
3069
|
+
if (e.autoDecompose !== undefined) config.engine.autoDecompose = !!e.autoDecompose;
|
|
3070
|
+
if (e.allowTempAgents !== undefined) config.engine.allowTempAgents = !!e.allowTempAgents;
|
|
3068
3071
|
}
|
|
3069
3072
|
|
|
3070
3073
|
if (body.claude) {
|
package/engine/shared.js
CHANGED
|
@@ -336,6 +336,7 @@ const ENGINE_DEFAULTS = {
|
|
|
336
336
|
shutdownTimeout: 300000, // 5min — max wait for active agents during graceful shutdown
|
|
337
337
|
allowTempAgents: false, // opt-in: spawn ephemeral agents when all permanent agents are busy
|
|
338
338
|
autoDecompose: true, // auto-decompose implement:large items into sub-tasks
|
|
339
|
+
autoApprovePlans: false, // auto-approve PRDs without waiting for human approval
|
|
339
340
|
meetingRoundTimeout: 600000, // 10min per meeting round before auto-advance
|
|
340
341
|
};
|
|
341
342
|
|
package/engine.js
CHANGED
|
@@ -1031,8 +1031,19 @@ function materializePlansAsWorkItems(config) {
|
|
|
1031
1031
|
// Human approval gate: plans start as 'awaiting-approval' and must be approved before work begins
|
|
1032
1032
|
// Plans without a status (legacy) or with status 'approved' are allowed through
|
|
1033
1033
|
const planStatus = plan.status || (plan.requires_approval ? 'awaiting-approval' : null);
|
|
1034
|
-
if (planStatus === 'awaiting-approval'
|
|
1035
|
-
|
|
1034
|
+
if (planStatus === 'awaiting-approval') {
|
|
1035
|
+
if (config.engine?.autoApprovePlans) {
|
|
1036
|
+
plan.status = 'approved';
|
|
1037
|
+
plan.approvedAt = new Date().toISOString();
|
|
1038
|
+
plan.approvedBy = 'auto-mode';
|
|
1039
|
+
safeWrite(prdPath, plan);
|
|
1040
|
+
log('info', `Auto-approved plan: ${file}`);
|
|
1041
|
+
} else {
|
|
1042
|
+
continue; // Skip — waiting for human approval
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
if (planStatus === 'paused' || planStatus === 'rejected' || planStatus === 'revision-requested') {
|
|
1046
|
+
continue; // Skip — paused or revision requested
|
|
1036
1047
|
}
|
|
1037
1048
|
// Stale PRDs: source plan was revised — don't materialize NEW items until user regenerates
|
|
1038
1049
|
if (plan.planStale) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.115",
|
|
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"
|