@yemi33/minions 0.1.845 → 0.1.847

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 CHANGED
@@ -1,13 +1,15 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.845 (2026-04-11)
3
+ ## 0.1.847 (2026-04-11)
4
4
 
5
5
  ### Features
6
+ - optimistic UI updates for all remaining dashboard pages
6
7
  - optimistic UI updates for all dashboard actions
7
8
  - per-item Re-open button on done PRD items (deterministic fallback)
8
9
  - stale PRD shows Regenerate PRD + Resume as-is buttons
9
10
 
10
11
  ### Fixes
12
+ - KB create modal stays open on API error for retry
11
13
  - steering resume failure no longer silently marked as SUCCESS
12
14
  - cli.js reads PID files from wrong directory on reattach
13
15
  - revert modal CSS changes from 227779d that broke plan modal layout
@@ -123,6 +123,14 @@ async function modalSaveEdit() {
123
123
  const body = document.getElementById('modal-body');
124
124
  const content = body.innerText;
125
125
 
126
+ body.contentEditable = 'false';
127
+ body.style.border = '';
128
+ body.style.padding = '';
129
+ document.getElementById('modal-edit-btn').style.display = '';
130
+ document.getElementById('modal-save-btn').style.display = 'none';
131
+ document.getElementById('modal-cancel-edit-btn').style.display = 'none';
132
+ _modalDocContext.content = content;
133
+ showToast('cmd-toast', 'Team Notes saved', true);
126
134
  try {
127
135
  const res = await fetch('/api/notes-save', {
128
136
  method: 'POST',
@@ -130,18 +138,9 @@ async function modalSaveEdit() {
130
138
  body: JSON.stringify({ file: _modalEditable, content }),
131
139
  });
132
140
  const data = await res.json();
133
- if (!res.ok) throw new Error(data.error || 'Save failed');
134
-
135
- body.contentEditable = 'false';
136
- body.style.border = '';
137
- body.style.padding = '';
138
- document.getElementById('modal-edit-btn').style.display = '';
139
- document.getElementById('modal-save-btn').style.display = 'none';
140
- document.getElementById('modal-cancel-edit-btn').style.display = 'none';
141
- _modalDocContext.content = content;
142
- showToast('cmd-toast', 'Team Notes saved', true);
141
+ if (!res.ok) showToast('cmd-toast', 'Save failed: ' + (data.error || 'unknown'), false);
143
142
  } catch (e) {
144
- showToast('cmd-toast', 'Error: ' + e.message, false);
143
+ showToast('cmd-toast', 'Save error: ' + e.message, false);
145
144
  }
146
145
  }
147
146
 
@@ -161,13 +160,14 @@ async function deleteInboxItem(name) {
161
160
  markDeleted('inbox:' + name);
162
161
  const card = document.querySelector('.inbox-item[data-file="notes/inbox/' + CSS.escape(name) + '"]');
163
162
  if (card) card.remove();
163
+ showToast('cmd-toast', 'Inbox item deleted', true);
164
164
  try {
165
165
  const res = await fetch('/api/inbox/delete', {
166
166
  method: 'POST', headers: { 'Content-Type': 'application/json' },
167
167
  body: JSON.stringify({ name })
168
168
  });
169
- if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Delete failed: ' + (d.error || 'unknown')); refresh(); }
170
- } catch (e) { alert('Delete error: ' + e.message); refresh(); }
169
+ if (!res.ok) { const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Delete failed: ' + (d.error || 'unknown'), false); refresh(); }
170
+ } catch (e) { showToast('cmd-toast', 'Delete error: ' + e.message, false); refresh(); }
171
171
  }
172
172
 
173
173
  async function openInboxInExplorer(name) {
@@ -217,8 +217,8 @@ async function submitQuickNote() {
217
217
  if (link && !link.querySelector('.notif-badge')) showNotifBadge(link);
218
218
  }
219
219
  }
220
- else { const d = await res.json().catch(() => ({})); alert('Note failed: ' + (d.error || 'unknown')); openQuickNoteModal(); }
221
- } catch (e) { alert('Error saving note: ' + e.message); openQuickNoteModal(); }
220
+ else { const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Note failed: ' + (d.error || 'unknown'), false); openQuickNoteModal(); }
221
+ } catch (e) { showToast('cmd-toast', 'Error saving note: ' + e.message, false); openQuickNoteModal(); }
222
222
  }
223
223
 
224
224
  async function doPromoteToKB(name, category) {
@@ -234,8 +234,8 @@ async function doPromoteToKB(name, category) {
234
234
  });
235
235
  const data = await res.json();
236
236
  if (res.ok) { refreshKnowledgeBase(); }
237
- else { alert('Failed: ' + (data.error || 'unknown')); refresh(); }
238
- } catch (e) { alert('Error: ' + e.message); refresh(); }
237
+ else { showToast('cmd-toast', 'Promote failed: ' + (data.error || 'unknown'), false); refresh(); }
238
+ } catch (e) { showToast('cmd-toast', 'Promote error: ' + e.message, false); refresh(); }
239
239
  }
240
240
 
241
241
  window.MinionsInbox = { renderInbox, promoteToKB, renderNotes, openNotesModal, modalToggleEdit, modalSaveEdit, modalCancelEdit, deleteInboxItem, openInboxInExplorer, openQuickNoteModal, submitQuickNote, doPromoteToKB };
@@ -143,12 +143,12 @@ async function kbSweep() {
143
143
  btn.textContent = 'sweeping...';
144
144
  btn.style.color = 'var(--blue)';
145
145
  try {
146
+ showToast('cmd-toast', 'KB sweep started', true);
146
147
  const res = await fetch('/api/knowledge/sweep', { method: 'POST', signal: AbortSignal.timeout(300000), headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pinnedKeys: getPinnedItems().filter(function(k) { return k.startsWith('knowledge/'); }) }) });
147
148
  const data = await res.json();
148
149
  if (data.ok) {
149
150
  btn.textContent = 'done';
150
151
  btn.style.color = 'var(--green)';
151
- showToast('cmd-toast', 'KB sweep: ' + (data.summary || 'complete'), true);
152
152
  refreshKnowledgeBase();
153
153
  } else {
154
154
  btn.style.color = 'var(--red)';
@@ -202,13 +202,14 @@ async function submitKbEntry() {
202
202
  const content = document.getElementById('kb-new-content').value;
203
203
  if (!title || !content) { if (btn) { btn.disabled = false; btn.textContent = 'Create'; } alert('Title and content are required'); return; }
204
204
  try {
205
+ showToast('cmd-toast', 'KB entry created', true);
205
206
  const res = await fetch('/api/knowledge', {
206
207
  method: 'POST', headers: { 'Content-Type': 'application/json' },
207
208
  body: JSON.stringify({ category, title, content })
208
209
  });
209
- if (res.ok) { closeModal(); refreshKnowledgeBase(); showToast('cmd-toast', 'KB entry created', true); }
210
- else { const d = await res.json().catch(() => ({})); alert('KB create failed: ' + (d.error || 'unknown')); }
211
- } catch (e) { alert('Error: ' + e.message); }
210
+ if (res.ok) { closeModal(); refreshKnowledgeBase(); }
211
+ else { if (btn) { btn.disabled = false; btn.textContent = 'Create'; } const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'KB create failed: ' + (d.error || 'unknown'), false); }
212
+ } catch (e) { if (btn) { btn.disabled = false; btn.textContent = 'Create'; } showToast('cmd-toast', 'Error: ' + e.message, false); }
212
213
  }
213
214
 
214
215
  async function kbOpenItem(category, file) {
@@ -277,8 +277,8 @@ async function _submitCreateMeeting() {
277
277
  });
278
278
  const data = await res.json();
279
279
  if (res.ok) { wakeEngine(); refresh(); }
280
- else { alert('Failed: ' + (data.error || 'unknown')); openCreateMeetingModal(); }
281
- } catch (e) { alert('Error: ' + e.message); openCreateMeetingModal(); }
280
+ else { showToast('cmd-toast', 'Failed: ' + (data.error || 'unknown'), false); openCreateMeetingModal(); }
281
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); openCreateMeetingModal(); }
282
282
  }
283
283
 
284
284
  async function _submitMeetingNote(id, btn) {
@@ -287,52 +287,53 @@ async function _submitMeetingNote(id, btn) {
287
287
  const note = input.value.trim();
288
288
  input.value = '';
289
289
  if (btn) { btn.textContent = 'Adding...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
290
+ showToast('cmd-toast', 'Note added', true);
290
291
  try {
291
292
  const res = await fetch('/api/meetings/note', {
292
293
  method: 'POST', headers: { 'Content-Type': 'application/json' },
293
294
  body: JSON.stringify({ id, note })
294
295
  });
295
- if (res.ok) { showToast('cmd-toast', 'Note added', true); }
296
- else { input.value = note; alert('Failed to add note'); }
297
- } catch (e) { input.value = note; alert('Error: ' + e.message); }
296
+ if (res.ok) { /* already toasted */ }
297
+ else { input.value = note; showToast('cmd-toast', 'Failed to add note', false); }
298
+ } catch (e) { input.value = note; showToast('cmd-toast', 'Error: ' + e.message, false); }
298
299
  if (btn) { btn.textContent = 'Add Note'; btn.style.pointerEvents = ''; btn.style.opacity = ''; }
299
300
  }
300
301
 
301
302
  async function _advanceMeeting(id, btn) {
302
303
  if (!confirm('Skip to next round? Agents that haven\'t finished will be skipped.')) return;
303
304
  if (btn) { btn.textContent = 'Advancing...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
305
+ showToast('cmd-toast', 'Advanced to next round', true);
304
306
  try {
305
307
  const res = await fetch('/api/meetings/advance', {
306
308
  method: 'POST', headers: { 'Content-Type': 'application/json' },
307
309
  body: JSON.stringify({ id })
308
310
  });
309
311
  if (res.ok) {
310
- showToast('cmd-toast', 'Advanced to next round', true);
311
312
  wakeEngine();
312
313
  } else {
313
314
  const d = await res.json().catch(function() { return {}; });
314
- alert('Advance failed: ' + (d.error || 'unknown'));
315
+ showToast('cmd-toast', 'Advance failed: ' + (d.error || 'unknown'), false);
315
316
  }
316
- } catch (e) { alert('Error: ' + e.message); }
317
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); }
317
318
  if (btn) { btn.textContent = 'Skip to Next Round'; btn.style.pointerEvents = ''; btn.style.opacity = ''; }
318
319
  }
319
320
 
320
321
  async function _endMeeting(id, btn) {
321
322
  if (!confirm('End this meeting? Current round will be stopped.')) return;
322
323
  if (btn) { btn.textContent = 'Ending...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
324
+ showToast('cmd-toast', 'Meeting ended', true);
323
325
  try {
324
326
  const res = await fetch('/api/meetings/end', {
325
327
  method: 'POST', headers: { 'Content-Type': 'application/json' },
326
328
  body: JSON.stringify({ id })
327
329
  });
328
- if (res.ok) {
329
- showToast('cmd-toast', 'Meeting ended', true);
330
- } else {
330
+ if (res.ok) { /* already toasted */ }
331
+ else {
331
332
  const d = await res.json().catch(function() { return {}; });
332
- alert('End failed: ' + (d.error || 'unknown'));
333
+ showToast('cmd-toast', 'End failed: ' + (d.error || 'unknown'), false);
333
334
  if (btn) { btn.textContent = 'End Meeting'; btn.style.pointerEvents = ''; btn.style.opacity = ''; }
334
335
  }
335
- } catch (e) { alert('Error: ' + e.message); if (btn) { btn.textContent = 'End Meeting'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
336
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); if (btn) { btn.textContent = 'End Meeting'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
336
337
  }
337
338
 
338
339
  async function _archiveMeeting(id) {
@@ -345,8 +346,8 @@ async function _archiveMeeting(id) {
345
346
  method: 'POST', headers: { 'Content-Type': 'application/json' },
346
347
  body: JSON.stringify({ id })
347
348
  });
348
- if (!res.ok) { _deletedIds.delete('mtg:' + id); const d = await res.json().catch(() => ({})); alert('Failed: ' + (d.error || 'unknown')); refresh(); }
349
- } catch (e) { _deletedIds.delete('mtg:' + id); alert('Error: ' + e.message); refresh(); }
349
+ if (!res.ok) { _deletedIds.delete('mtg:' + id); const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Failed: ' + (d.error || 'unknown'), false); refresh(); }
350
+ } catch (e) { _deletedIds.delete('mtg:' + id); showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
350
351
  }
351
352
 
352
353
  async function _unarchiveMeeting(id) {
@@ -358,8 +359,8 @@ async function _unarchiveMeeting(id) {
358
359
  method: 'POST', headers: { 'Content-Type': 'application/json' },
359
360
  body: JSON.stringify({ id })
360
361
  });
361
- if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Unarchive failed: ' + (d.error || 'unknown')); refresh(); }
362
- } catch (e) { alert('Error: ' + e.message); refresh(); }
362
+ if (!res.ok) { const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Unarchive failed: ' + (d.error || 'unknown'), false); refresh(); }
363
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
363
364
  }
364
365
 
365
366
  function _viewPlanWithBack(file, meetingId) {
@@ -404,7 +405,7 @@ async function _createPlanFromMeeting(id, btn) {
404
405
  try {
405
406
  const res = await fetch('/api/meetings/' + encodeURIComponent(id));
406
407
  const data = await res.json();
407
- if (!data.meeting) { resetBtn(); alert('Meeting not found'); return; }
408
+ if (!data.meeting) { resetBtn(); showToast('cmd-toast', 'Meeting not found', false); return; }
408
409
  const m = data.meeting;
409
410
 
410
411
  // Check if a plan already exists for this meeting
@@ -415,6 +416,7 @@ async function _createPlanFromMeeting(id, btn) {
415
416
  }
416
417
 
417
418
  if (btn) btn.textContent = 'Generating plan...';
419
+ showToast('cmd-toast', 'Generating plan from meeting...', true);
418
420
 
419
421
  // Use doc-chat to generate a structured plan from the meeting
420
422
  const transcript = (m.transcript || []).map(function(t) {
@@ -439,7 +441,7 @@ async function _createPlanFromMeeting(id, btn) {
439
441
  })
440
442
  });
441
443
  const genData = await genRes.json();
442
- if (!genRes.ok || !genData.ok) { resetBtn(); alert('Failed to generate plan: ' + (genData.error || 'unknown')); return; }
444
+ if (!genRes.ok || !genData.ok) { resetBtn(); showToast('cmd-toast', 'Failed to generate plan: ' + (genData.error || 'unknown'), false); return; }
443
445
 
444
446
  const planContent = genData.answer || '';
445
447
  const title = 'Meeting follow-up: ' + (m.title || id);
@@ -464,9 +466,9 @@ async function _createPlanFromMeeting(id, btn) {
464
466
  }
465
467
  } else {
466
468
  resetBtn();
467
- alert('Failed: ' + (planData.error || 'unknown'));
469
+ showToast('cmd-toast', 'Failed: ' + (planData.error || 'unknown'), false);
468
470
  }
469
- } catch (e) { resetBtn(); alert('Error: ' + e.message); }
471
+ } catch (e) { resetBtn(); showToast('cmd-toast', 'Error: ' + e.message, false); }
470
472
  }
471
473
 
472
474
  async function _deleteMeeting(id) {
@@ -474,13 +476,14 @@ async function _deleteMeeting(id) {
474
476
  markDeleted('mtg:' + id);
475
477
  try { closeModal(); } catch { /* may not be open */ }
476
478
  document.querySelectorAll('[onclick*="openMeetingDetail(\'' + id + '\')"]').forEach(function(el) { el.remove(); });
479
+ showToast('cmd-toast', 'Meeting deleted', true);
477
480
  try {
478
481
  const res = await fetch('/api/meetings/delete', {
479
482
  method: 'POST', headers: { 'Content-Type': 'application/json' },
480
483
  body: JSON.stringify({ id })
481
484
  });
482
- if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Failed: ' + (d.error || 'unknown')); refresh(); }
483
- } catch (e) { alert('Error: ' + e.message); refresh(); }
485
+ if (!res.ok) { const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Failed: ' + (d.error || 'unknown'), false); refresh(); }
486
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
484
487
  }
485
488
 
486
489
  window.MinionsMeetings = { renderMeetings, openMeetingDetail, openCreateMeetingModal };
@@ -45,18 +45,19 @@ async function submitPinnedNote(e) {
45
45
  showToast('cmd-toast', 'Note pinned', true);
46
46
  try {
47
47
  const res = await fetch('/api/pinned', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title, content, level }) });
48
- if (res.ok) { refresh(); } else { const d = await res.json().catch(() => ({})); alert('Pin failed: ' + (d.error || 'unknown')); openPinNoteModal(); }
49
- } catch (e) { alert('Error: ' + e.message); openPinNoteModal(); }
48
+ if (res.ok) { refresh(); } else { const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Pin failed: ' + (d.error || 'unknown'), false); openPinNoteModal(); }
49
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); openPinNoteModal(); }
50
50
  }
51
51
 
52
52
  async function removePinnedNote(title) {
53
53
  if (!confirm('Unpin "' + title + '"?')) return;
54
54
  markDeleted('pin:' + title);
55
55
  const btn = (window.event)?.target; if (btn) { const card = btn.closest('.pinned-card') || btn.parentElement?.parentElement; if (card) card.remove(); }
56
+ showToast('cmd-toast', 'Note unpinned', true);
56
57
  try {
57
58
  const res = await fetch('/api/pinned/remove', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title }) });
58
- if (!res.ok) { alert('Unpin failed'); refresh(); }
59
- } catch (e) { alert('Error: ' + e.message); refresh(); }
59
+ if (!res.ok) { showToast('cmd-toast', 'Unpin failed', false); refresh(); }
60
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
60
61
  }
61
62
 
62
63
  function openPinnedView(idx) {
@@ -446,67 +446,66 @@ async function _refreshPipelineDetail(id) {
446
446
 
447
447
  async function _triggerPipeline(id, btn) {
448
448
  if (btn) { btn.textContent = 'Starting...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
449
+ showToast('cmd-toast', 'Pipeline triggered', true);
449
450
  try {
450
451
  var res = await fetch('/api/pipelines/trigger', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) });
451
- var d = await res.json();
452
- if (res.ok) { showToast('cmd-toast', 'Pipeline triggered: ' + (d.runId || ''), true); refresh(); await _refreshPipelineDetail(id); }
453
- else { if (btn) { btn.textContent = 'Run Now'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } alert('Failed: ' + (d.error || 'unknown')); }
454
- } catch (e) { if (btn) { btn.textContent = 'Run Now'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } alert('Error: ' + e.message); }
452
+ if (res.ok) { refresh(); await _refreshPipelineDetail(id); }
453
+ else { var d = await res.json().catch(function() { return {}; }); if (btn) { btn.textContent = 'Run Now'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } showToast('cmd-toast', 'Failed: ' + (d.error || 'unknown'), false); }
454
+ } catch (e) { if (btn) { btn.textContent = 'Run Now'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } showToast('cmd-toast', 'Error: ' + e.message, false); }
455
455
  }
456
456
 
457
457
  async function _abortPipeline(id, btn) {
458
458
  if (!confirm('Abort the active run for "' + id + '"?')) return;
459
459
  if (btn) { btn.textContent = 'Aborting...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
460
+ showToast('cmd-toast', 'Pipeline aborted', true);
460
461
  try {
461
462
  var res = await fetch('/api/pipelines/abort', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) });
462
463
  if (res.ok) {
463
- var d = await res.json().catch(function() { return {}; });
464
- showToast('cmd-toast', 'Pipeline aborted — ' + (d.cancelledWorkItems || 0) + ' work items cancelled', true);
465
464
  refresh();
466
465
  await _refreshPipelineDetail(id);
467
- } else { var d = await res.json().catch(function() { return {}; }); alert('Abort failed: ' + (d.error || 'unknown')); if (btn) { btn.textContent = 'Abort'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
468
- } catch (e) { alert('Error: ' + e.message); if (btn) { btn.textContent = 'Abort'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
466
+ } else { var d = await res.json().catch(function() { return {}; }); showToast('cmd-toast', 'Abort failed: ' + (d.error || 'unknown'), false); if (btn) { btn.textContent = 'Abort'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
467
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); if (btn) { btn.textContent = 'Abort'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
469
468
  }
470
469
 
471
470
  async function _retriggerPipeline(id, btn) {
472
471
  if (!confirm('Abort current run and start a fresh one for "' + id + '"?')) return;
473
472
  if (btn) { btn.textContent = 'Retriggering...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
473
+ showToast('cmd-toast', 'Pipeline retriggered', true);
474
474
  try {
475
475
  var res = await fetch('/api/pipelines/retrigger', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) });
476
- var d = await res.json();
477
476
  if (res.ok) {
478
- showToast('cmd-toast', 'Pipeline retriggered: ' + (d.runId || ''), true);
479
477
  refresh();
480
478
  await _refreshPipelineDetail(id);
481
- } else { alert('Retrigger failed: ' + (d.error || 'unknown')); if (btn) { btn.textContent = 'Retrigger'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
482
- } catch (e) { alert('Error: ' + e.message); if (btn) { btn.textContent = 'Retrigger'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
479
+ } else { var d = await res.json().catch(function() { return {}; }); showToast('cmd-toast', 'Retrigger failed: ' + (d.error || 'unknown'), false); if (btn) { btn.textContent = 'Retrigger'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
480
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); if (btn) { btn.textContent = 'Retrigger'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
483
481
  }
484
482
 
485
483
  async function _togglePipelineEnabled(id, enabled, btn) {
486
484
  if (btn) { btn.textContent = enabled ? 'Enabling...' : 'Disabling...'; btn.style.pointerEvents = 'none'; }
485
+ showToast('cmd-toast', enabled ? 'Pipeline enabled' : 'Pipeline disabled', true);
487
486
  try {
488
487
  var res = await fetch('/api/pipelines/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id, enabled: enabled }) });
489
- if (res.ok) { showToast('cmd-toast', enabled ? 'Pipeline enabled' : 'Pipeline disabled', true); refresh(); }
490
- else { alert('Failed'); }
491
- } catch (e) { alert('Error: ' + e.message); }
488
+ if (res.ok) { refresh(); }
489
+ else { showToast('cmd-toast', 'Failed to ' + (enabled ? 'enable' : 'disable') + ' pipeline', false); }
490
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); }
492
491
  if (btn) { btn.textContent = enabled ? 'Disable' : 'Enable'; btn.style.pointerEvents = ''; }
493
492
  }
494
493
 
495
494
  async function _continuePipeline(id, stageId, btn) {
496
495
  if (btn) { btn.textContent = 'Continuing...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
496
+ showToast('cmd-toast', 'Stage continued — dispatching next tick', true);
497
497
  try {
498
498
  var res = await fetch('/api/pipelines/continue', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id, stageId: stageId }) });
499
499
  if (res.ok) {
500
- showToast('cmd-toast', 'Stage continued — dispatching next tick', true);
501
500
  if (btn) { btn.textContent = '\u2713 Continued'; btn.style.color = 'var(--green)'; btn.style.borderColor = 'var(--green)'; btn.style.opacity = '1'; }
502
501
  // Immediate refresh — no waiting for 4s poll
503
502
  await _refreshPipelineDetail(id);
504
503
  } else {
505
- var d = await res.json().catch(function() { return {}; }); alert('Failed: ' + (d.error || 'unknown'));
504
+ var d = await res.json().catch(function() { return {}; }); showToast('cmd-toast', 'Failed: ' + (d.error || 'unknown'), false);
506
505
  if (btn) { btn.textContent = 'Continue'; btn.style.pointerEvents = ''; btn.style.opacity = ''; }
507
506
  }
508
507
  } catch (e) {
509
- alert('Error: ' + e.message);
508
+ showToast('cmd-toast', 'Error: ' + e.message, false);
510
509
  if (btn) { btn.textContent = 'Continue'; btn.style.pointerEvents = ''; btn.style.opacity = ''; }
511
510
  }
512
511
  }
@@ -515,10 +514,11 @@ async function _deletePipelineConfirm(id) {
515
514
  if (!confirm('Delete pipeline "' + id + '"?')) return;
516
515
  markDeleted('pipeline:' + id);
517
516
  try { closeModal(); } catch {}
517
+ showToast('cmd-toast', 'Pipeline deleted', true);
518
518
  try {
519
519
  var res = await fetch('/api/pipelines/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) });
520
- if (!res.ok) { alert('Delete failed'); refresh(); }
521
- } catch (e) { alert('Error: ' + e.message); refresh(); }
520
+ if (!res.ok) { showToast('cmd-toast', 'Delete failed', false); refresh(); }
521
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
522
522
  }
523
523
 
524
524
  function openCreatePipelineModal() {
@@ -613,8 +613,8 @@ async function _submitCreatePipeline() {
613
613
  showToast('cmd-toast', 'Pipeline created', true);
614
614
  try {
615
615
  var res = await fetch('/api/pipelines', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
616
- if (res.ok) { refresh(); } else { var d = await res.json().catch(function() { return {}; }); alert('Failed: ' + (d.error || 'unknown')); openCreatePipelineModal(); }
617
- } catch (e) { alert('Error: ' + e.message); openCreatePipelineModal(); }
616
+ if (res.ok) { refresh(); } else { var d = await res.json().catch(function() { return {}; }); showToast('cmd-toast', 'Failed: ' + (d.error || 'unknown'), false); openCreatePipelineModal(); }
617
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); openCreatePipelineModal(); }
618
618
  }
619
619
 
620
620
  function openEditPipelineModal(id) {
@@ -694,11 +694,13 @@ async function _submitEditPipeline() {
694
694
  if (!Array.isArray(stages) || stages.length === 0) { alert('Stages must be a non-empty array'); return; }
695
695
 
696
696
  var body = { id: id, title: title, stages: stages, trigger: cron ? { cron: cron } : null };
697
+ closeModal();
698
+ showToast('cmd-toast', 'Pipeline updated', true);
697
699
  try {
698
700
  var res = await fetch('/api/pipelines/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
699
- if (res.ok) { closeModal(); showToast('cmd-toast', 'Pipeline updated', true); refresh(); }
700
- else { var d = await res.json().catch(function() { return {}; }); alert('Failed: ' + (d.error || 'unknown')); }
701
- } catch (e) { alert('Error: ' + e.message); }
701
+ if (res.ok) { refresh(); }
702
+ else { var d = await res.json().catch(function() { return {}; }); showToast('cmd-toast', 'Failed: ' + (d.error || 'unknown'), false); }
703
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); }
702
704
  }
703
705
 
704
706
  window.MinionsPipelines = { renderPipelines, openPipelineDetail, openCreatePipelineModal, openEditPipelineModal };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.845",
3
+ "version": "0.1.847",
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"