@yemi33/minions 0.1.366 → 0.1.368

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,8 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.366 (2026-04-06)
3
+ ## 0.1.368 (2026-04-06)
4
4
 
5
5
  ### Fixes
6
+ - dashboard audit pass 2 — CC error handling, charter edits, archived PRDs (#214)
7
+
8
+ ## 0.1.367 (2026-04-06)
9
+
10
+ ### Fixes
11
+ - low-severity dashboard polish — null guards, event params, UI gaps (#213)
6
12
  - medium dashboard bugs — settings reset, work items, schedules, plans (#212)
7
13
  - XSS vulnerabilities across dashboard components (#211)
8
14
 
@@ -348,6 +348,12 @@ function ccRetryLast() {
348
348
  _ccDoSend(text.trim());
349
349
  }
350
350
 
351
+ async function _ccFetch(url, body) {
352
+ const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
353
+ if (!res.ok) { const d = await res.json().catch(() => ({})); throw new Error(d.error || 'Request failed (' + res.status + ')'); }
354
+ return res;
355
+ }
356
+
351
357
  async function ccExecuteAction(action) {
352
358
  const msgs = document.getElementById('cc-messages');
353
359
  const status = document.createElement('div');
@@ -356,13 +362,10 @@ async function ccExecuteAction(action) {
356
362
  try {
357
363
  switch (action.type) {
358
364
  case 'dispatch': {
359
- const res = await fetch('/api/work-items', {
360
- method: 'POST', headers: { 'Content-Type': 'application/json' },
361
- body: JSON.stringify({
365
+ const res = await _ccFetch('/api/work-items', {
362
366
  title: action.title, type: action.workType || 'implement',
363
367
  priority: action.priority || 'medium', description: action.description || '',
364
368
  project: action.project || '', agents: action.agents || [],
365
- })
366
369
  });
367
370
  const d = await res.json();
368
371
  status.innerHTML = '&#10003; Dispatched: <strong>' + escHtml(d.id || action.title) + '</strong>';
@@ -371,49 +374,33 @@ async function ccExecuteAction(action) {
371
374
  break;
372
375
  }
373
376
  case 'note': {
374
- const today = new Date().toISOString().slice(0, 10);
375
- await fetch('/api/notes', {
376
- method: 'POST', headers: { 'Content-Type': 'application/json' },
377
- body: JSON.stringify({ title: action.title, what: action.content || action.description, author: 'command-center' })
378
- });
377
+ await _ccFetch('/api/notes', { title: action.title, what: action.content || action.description, author: 'command-center' });
379
378
  status.innerHTML = '&#10003; Note saved: <strong>' + escHtml(action.title) + '</strong>';
380
379
  status.style.color = 'var(--green)';
381
380
  break;
382
381
  }
383
382
  case 'pin': {
384
- await fetch('/api/pinned', {
385
- method: 'POST', headers: { 'Content-Type': 'application/json' },
386
- body: JSON.stringify({ title: action.title, content: action.content || action.description, level: action.level || '' })
387
- });
383
+ await _ccFetch('/api/pinned', { title: action.title, content: action.content || action.description, level: action.level || '' });
388
384
  status.innerHTML = '&#x1F4CC; Pinned: <strong>' + escHtml(action.title) + '</strong> — visible to all agents';
389
385
  status.style.color = 'var(--green)';
390
386
  refresh();
391
387
  break;
392
388
  }
393
389
  case 'plan': {
394
- await fetch('/api/plan', {
395
- method: 'POST', headers: { 'Content-Type': 'application/json' },
396
- body: JSON.stringify({ title: action.title, description: action.description, project: action.project, branchStrategy: action.branchStrategy || 'parallel' })
397
- });
390
+ await _ccFetch('/api/plan', { title: action.title, description: action.description, project: action.project, branchStrategy: action.branchStrategy || 'parallel' });
398
391
  status.innerHTML = '&#10003; Plan queued: <strong>' + escHtml(action.title) + '</strong>';
399
392
  status.style.color = 'var(--green)';
400
393
  break;
401
394
  }
402
395
  case 'cancel': {
403
- await fetch('/api/agents/cancel', {
404
- method: 'POST', headers: { 'Content-Type': 'application/json' },
405
- body: JSON.stringify({ agentId: action.agent, reason: action.reason || 'Cancelled via command center' })
406
- });
396
+ await _ccFetch('/api/agents/cancel', { agentId: action.agent, reason: action.reason || 'Cancelled via command center' });
407
397
  status.innerHTML = '&#10003; Cancelled agent: <strong>' + escHtml(action.agent) + '</strong>';
408
398
  status.style.color = 'var(--orange)';
409
399
  break;
410
400
  }
411
401
  case 'retry': {
412
402
  for (const id of (action.ids || [])) {
413
- await fetch('/api/work-items/retry', {
414
- method: 'POST', headers: { 'Content-Type': 'application/json' },
415
- body: JSON.stringify({ id, source: '' })
416
- });
403
+ await _ccFetch('/api/work-items/retry', { id, source: '' });
417
404
  }
418
405
  status.innerHTML = '&#10003; Retried: <strong>' + escHtml((action.ids || []).join(', ')) + '</strong>';
419
406
  status.style.color = 'var(--green)';
@@ -421,46 +408,31 @@ async function ccExecuteAction(action) {
421
408
  break;
422
409
  }
423
410
  case 'pause-plan': {
424
- await fetch('/api/plans/pause', {
425
- method: 'POST', headers: { 'Content-Type': 'application/json' },
426
- body: JSON.stringify({ file: action.file })
427
- });
411
+ await _ccFetch('/api/plans/pause', { file: action.file });
428
412
  status.innerHTML = '&#10003; Paused plan: <strong>' + escHtml(action.file) + '</strong>';
429
413
  status.style.color = 'var(--orange)';
430
414
  break;
431
415
  }
432
416
  case 'approve-plan': {
433
- await fetch('/api/plans/approve', {
434
- method: 'POST', headers: { 'Content-Type': 'application/json' },
435
- body: JSON.stringify({ file: action.file })
436
- });
417
+ await _ccFetch('/api/plans/approve', { file: action.file });
437
418
  status.innerHTML = '&#10003; Approved plan: <strong>' + escHtml(action.file) + '</strong>';
438
419
  status.style.color = 'var(--green)';
439
420
  break;
440
421
  }
441
422
  case 'edit-prd-item': {
442
- await fetch('/api/prd-items/update', {
443
- method: 'POST', headers: { 'Content-Type': 'application/json' },
444
- body: JSON.stringify({ source: action.source, itemId: action.itemId, name: action.name, description: action.description, priority: action.priority, estimated_complexity: action.complexity })
445
- });
423
+ await _ccFetch('/api/prd-items/update', { source: action.source, itemId: action.itemId, name: action.name, description: action.description, priority: action.priority, estimated_complexity: action.complexity });
446
424
  status.innerHTML = '&#10003; Updated PRD item: <strong>' + escHtml(action.itemId) + '</strong>';
447
425
  status.style.color = 'var(--green)';
448
426
  break;
449
427
  }
450
428
  case 'remove-prd-item': {
451
- await fetch('/api/prd-items/remove', {
452
- method: 'POST', headers: { 'Content-Type': 'application/json' },
453
- body: JSON.stringify({ source: action.source, itemId: action.itemId })
454
- });
429
+ await _ccFetch('/api/prd-items/remove', { source: action.source, itemId: action.itemId });
455
430
  status.innerHTML = '&#10003; Removed PRD item: <strong>' + escHtml(action.itemId) + '</strong>';
456
431
  status.style.color = 'var(--orange)';
457
432
  break;
458
433
  }
459
434
  case 'delete-work-item': {
460
- await fetch('/api/work-items/delete', {
461
- method: 'POST', headers: { 'Content-Type': 'application/json' },
462
- body: JSON.stringify({ id: action.id, source: action.source || '' })
463
- });
435
+ await _ccFetch('/api/work-items/delete', { id: action.id, source: action.source || '' });
464
436
  status.innerHTML = '&#10003; Deleted work item: <strong>' + escHtml(action.id) + '</strong>';
465
437
  status.style.color = 'var(--orange)';
466
438
  break;
@@ -585,48 +557,33 @@ async function ccExecuteAction(action) {
585
557
  break;
586
558
  }
587
559
  case 'unpin': {
588
- await fetch('/api/pinned/remove', {
589
- method: 'POST', headers: { 'Content-Type': 'application/json' },
590
- body: JSON.stringify({ title: action.title })
591
- });
560
+ await _ccFetch('/api/pinned/remove', { title: action.title });
592
561
  status.innerHTML = '&#10003; Unpinned: <strong>' + escHtml(action.title) + '</strong>';
593
562
  status.style.color = 'var(--green)';
594
563
  refresh();
595
564
  break;
596
565
  }
597
566
  case 'archive-plan': {
598
- await fetch('/api/plans/archive', {
599
- method: 'POST', headers: { 'Content-Type': 'application/json' },
600
- body: JSON.stringify({ file: action.file })
601
- });
567
+ await _ccFetch('/api/plans/archive', { file: action.file });
602
568
  status.innerHTML = '&#10003; Archived plan: <strong>' + escHtml(action.file) + '</strong>';
603
569
  status.style.color = 'var(--green)';
604
570
  refresh();
605
571
  break;
606
572
  }
607
573
  case 'reject-plan': {
608
- await fetch('/api/plans/reject', {
609
- method: 'POST', headers: { 'Content-Type': 'application/json' },
610
- body: JSON.stringify({ file: action.file, reason: action.reason || '' })
611
- });
574
+ await _ccFetch('/api/plans/reject', { file: action.file, reason: action.reason || '' });
612
575
  status.innerHTML = '&#10003; Rejected plan: <strong>' + escHtml(action.file) + '</strong>';
613
576
  status.style.color = 'var(--orange)';
614
577
  break;
615
578
  }
616
579
  case 'steer-agent': {
617
- await fetch('/api/agents/steer', {
618
- method: 'POST', headers: { 'Content-Type': 'application/json' },
619
- body: JSON.stringify({ agent: action.agent, message: action.message || action.content })
620
- });
580
+ await _ccFetch('/api/agents/steer', { agent: action.agent, message: action.message || action.content });
621
581
  status.innerHTML = '&#10003; Steering message sent to <strong>' + escHtml(action.agent) + '</strong>';
622
582
  status.style.color = 'var(--green)';
623
583
  break;
624
584
  }
625
585
  case 'add-meeting-note': {
626
- await fetch('/api/meetings/note', {
627
- method: 'POST', headers: { 'Content-Type': 'application/json' },
628
- body: JSON.stringify({ id: action.id, note: action.note || action.content })
629
- });
586
+ await _ccFetch('/api/meetings/note', { id: action.id, note: action.note || action.content });
630
587
  status.innerHTML = '&#10003; Note added to meeting <strong>' + escHtml(action.id) + '</strong>';
631
588
  status.style.color = 'var(--green)';
632
589
  break;
@@ -643,30 +600,21 @@ async function ccExecuteAction(action) {
643
600
  break;
644
601
  }
645
602
  case 'link-pr': {
646
- await fetch('/api/pull-requests/link', {
647
- method: 'POST', headers: { 'Content-Type': 'application/json' },
648
- body: JSON.stringify({ url: action.url, title: action.title || '', project: action.project || '', autoObserve: action.autoObserve !== false })
649
- });
603
+ await _ccFetch('/api/pull-requests/link', { url: action.url, title: action.title || '', project: action.project || '', autoObserve: action.autoObserve !== false });
650
604
  status.innerHTML = '&#10003; PR linked: <strong>' + escHtml(action.url) + '</strong>';
651
605
  status.style.color = 'var(--green)';
652
606
  refresh();
653
607
  break;
654
608
  }
655
609
  case 'archive-meeting': {
656
- await fetch('/api/meetings/archive', {
657
- method: 'POST', headers: { 'Content-Type': 'application/json' },
658
- body: JSON.stringify({ id: action.id })
659
- });
610
+ await _ccFetch('/api/meetings/archive', { id: action.id });
660
611
  status.innerHTML = '&#10003; Meeting archived: <strong>' + escHtml(action.id) + '</strong>';
661
612
  status.style.color = 'var(--green)';
662
613
  refresh();
663
614
  break;
664
615
  }
665
616
  case 'update-routing': {
666
- await fetch('/api/settings/routing', {
667
- method: 'POST', headers: { 'Content-Type': 'application/json' },
668
- body: JSON.stringify({ content: action.content })
669
- });
617
+ await _ccFetch('/api/settings/routing', { content: action.content });
670
618
  status.innerHTML = '&#10003; Routing updated';
671
619
  status.style.color = 'var(--green)';
672
620
  break;
@@ -1,5 +1,7 @@
1
1
  // dashboard/js/detail-panel.js — Agent detail panel extracted from dashboard.html
2
2
 
3
+ let _charterRawCache = ''; // stored outside DOM to survive innerHTML rewrites on tab switch
4
+
3
5
  function closeDetail() {
4
6
  document.getElementById('detail-overlay').classList.remove('open');
5
7
  document.getElementById('detail-panel').classList.remove('open');
@@ -88,7 +90,7 @@ function renderDetailContent(detail, tab) {
88
90
  '</div>' +
89
91
  '<div id="charter-view" class="section">' + renderMd(charterContent || 'No charter found. Click Edit to create one.') + '</div>' +
90
92
  '<textarea id="charter-editor" style="display:none;width:100%;min-height:300px;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-family:Consolas,monospace;font-size:12px;resize:vertical">' + escHtml(charterContent) + '</textarea>';
91
- el._charterRaw = charterContent;
93
+ _charterRawCache = charterContent;
92
94
  } else if (tab === 'history') {
93
95
  let html = '';
94
96
  // Recent dispatch results
@@ -126,7 +128,7 @@ function _toggleCharterEdit() {
126
128
 
127
129
  function _cancelCharterEdit() {
128
130
  const el = document.getElementById('detail-content');
129
- document.getElementById('charter-editor').value = el._charterRaw || '';
131
+ document.getElementById('charter-editor').value = _charterRawCache || '';
130
132
  document.getElementById('charter-view').style.display = '';
131
133
  document.getElementById('charter-editor').style.display = 'none';
132
134
  document.getElementById('charter-edit-btn').style.display = '';
@@ -146,7 +148,7 @@ async function _saveCharter() {
146
148
  });
147
149
  if (res.ok) {
148
150
  document.getElementById('charter-view').innerHTML = renderMd(content);
149
- document.getElementById('detail-content')._charterRaw = content;
151
+ _charterRawCache = content;
150
152
  _cancelCharterEdit();
151
153
  showToast('cmd-toast', 'Charter saved', true);
152
154
  } else {
@@ -6,9 +6,9 @@ let _completedPage = 0;
6
6
  let _logPage = 0;
7
7
 
8
8
  function _completedPrev() { if (_completedPage > 0) { _completedPage--; refresh(); } }
9
- function _completedNext() { _completedPage++; refresh(); }
9
+ function _completedNext() { _completedPage++; refresh(); } // clamped in renderDispatch
10
10
  function _logPrev() { if (_logPage > 0) { _logPage--; refresh(); } }
11
- function _logNext() { _logPage++; refresh(); }
11
+ function _logNext() { _logPage++; refresh(); } // clamped in renderEngineLog
12
12
 
13
13
  function renderEngineStatus(engine) {
14
14
  const badge = document.getElementById('engine-badge');
@@ -153,6 +153,7 @@ function renderDispatch(dispatch) {
153
153
 
154
154
  function renderEngineLog(log) {
155
155
  const el = document.getElementById('engine-log');
156
+ if (!el) return;
156
157
  if (!log || log.length === 0) {
157
158
  el.innerHTML = '<div class="empty">No log entries yet.</div>';
158
159
  return;
@@ -140,7 +140,7 @@ async function modalSaveEdit() {
140
140
  function modalCancelEdit() {
141
141
  const body = document.getElementById('modal-body');
142
142
  body.contentEditable = 'false';
143
- body.textContent = _modalDocContext.content; // revert
143
+ body.innerHTML = renderMd(_modalDocContext.content); // revert (render Markdown, not raw text)
144
144
  body.style.border = '';
145
145
  body.style.padding = '';
146
146
  document.getElementById('modal-edit-btn').style.display = '';
@@ -62,7 +62,7 @@ function renderKnowledgeBase() {
62
62
  if (!Array.isArray(catItems)) continue;
63
63
  for (const item of catItems) items.push({ ...item, category: cat });
64
64
  }
65
- items.sort((a, b) => b.date.localeCompare(a.date));
65
+ items.sort((a, b) => (b.date || '').localeCompare(a.date || ''));
66
66
  } else {
67
67
  items = (_kbData[_kbActiveTab] || []).map(i => ({ ...i, category: _kbActiveTab }));
68
68
  }
@@ -400,7 +400,11 @@ function _renderPlanModal(normalizedFile, raw, lastMod) {
400
400
  let text = '';
401
401
 
402
402
  if (normalizedFile.endsWith('.json')) {
403
- const plan = JSON.parse(raw);
403
+ let plan;
404
+ try { plan = JSON.parse(raw); } catch (e) {
405
+ document.getElementById('modal-body').innerHTML = '<p style="color:var(--red)">Failed to parse plan JSON: ' + escHtml(e.message) + '</p><pre style="font-size:10px;max-height:200px;overflow:auto">' + escHtml((raw || '').slice(0, 500)) + '</pre>';
406
+ return;
407
+ }
404
408
  title = plan.plan_summary || normalizedFile;
405
409
  const items = (plan.missing_features || []).map((f, i) =>
406
410
  (i + 1) + '. [' + f.id + '] ' + f.name + ' (' + (f.estimated_complexity || '?') + ', ' + (f.priority || '?') + ')' +
@@ -550,7 +550,14 @@ function showArchivedPrdDetail(idxOrFile) {
550
550
  }
551
551
 
552
552
  async function prdItemEdit(source, itemId) {
553
- const item = _prdItems.find(i => i.source === source && i.id === itemId);
553
+ let item = _prdItems.find(i => i.source === source && i.id === itemId);
554
+ // Also search archived groups if not found in active items
555
+ if (!item && window._archivedPrdGroups) {
556
+ for (const g of window._archivedPrdGroups) {
557
+ item = (g.items || []).find(i => i.source === source && i.id === itemId);
558
+ if (item) break;
559
+ }
560
+ }
554
561
  if (!item) return;
555
562
 
556
563
  // Look up work item and dispatch completion info
@@ -151,14 +151,14 @@ function editWorkItem(id, source) {
151
151
  '</label>' +
152
152
  '<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px">' +
153
153
  '<button onclick="closeModal()" class="pr-pager-btn" style="padding:6px 16px;font-size:var(--text-md)">Cancel</button>' +
154
- '<button onclick="submitWorkItemEdit(\'' + escHtml(id) + '\',\'' + escHtml(source || '') + '\')" style="padding:6px 16px;font-size:var(--text-md);background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Save</button>' +
154
+ '<button onclick="submitWorkItemEdit(\'' + escHtml(id) + '\',\'' + escHtml(source || '') + '\',event)" style="padding:6px 16px;font-size:var(--text-md);background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Save</button>' +
155
155
  '</div>' +
156
156
  '</div>';
157
157
  document.getElementById('modal').classList.add('open');
158
158
  }
159
159
 
160
- async function submitWorkItemEdit(id, source) {
161
- var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Saving...'; }
160
+ async function submitWorkItemEdit(id, source, e) {
161
+ var btn = (e || window.event)?.target; if (btn) { btn.disabled = true; btn.textContent = 'Saving...'; }
162
162
  const title = document.getElementById('wi-edit-title').value.trim();
163
163
  const description = document.getElementById('wi-edit-desc').value;
164
164
  const type = document.getElementById('wi-edit-type').value;
@@ -320,7 +320,7 @@ function openCreateWorkItemModal() {
320
320
  const typeOpts = ['implement', 'fix', 'explore', 'test', 'review', 'ask', 'plan', 'verify', 'decompose', 'meeting'].map(t =>
321
321
  '<option value="' + t + '"' + (t === 'implement' ? ' selected' : '') + '>' + t + '</option>'
322
322
  ).join('');
323
- const priOpts = ['high', 'medium', 'low'].map(p =>
323
+ const priOpts = ['critical', 'high', 'medium', 'low'].map(p =>
324
324
  '<option value="' + p + '"' + (p === 'medium' ? ' selected' : '') + '>' + p + '</option>'
325
325
  ).join('');
326
326
  const agentOpts = (typeof cmdAgents !== 'undefined' ? cmdAgents : []).map(a =>
@@ -349,7 +349,7 @@ function openCreateWorkItemModal() {
349
349
  '<label id="wi-new-skippr-row" style="color:var(--text);font-size:var(--text-md);display:flex;gap:8px;align-items:center;cursor:pointer"><input type="checkbox" id="wi-new-skippr"> Skip PR creation (push branch only)</label>' +
350
350
  '<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:4px">' +
351
351
  '<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
352
- '<button onclick="_submitCreateWorkItem()" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Create</button>' +
352
+ '<button onclick="_submitCreateWorkItem(event)" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Create</button>' +
353
353
  '</div>' +
354
354
  '</div>';
355
355
  document.getElementById('modal').classList.add('open');
@@ -365,8 +365,8 @@ function openCreateWorkItemModal() {
365
365
  setTimeout(() => document.getElementById('wi-new-title')?.focus(), 100);
366
366
  }
367
367
 
368
- async function _submitCreateWorkItem() {
369
- var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Creating...'; }
368
+ async function _submitCreateWorkItem(e) {
369
+ var btn = (e || window.event)?.target; if (btn) { btn.disabled = true; btn.textContent = 'Creating...'; }
370
370
  const title = document.getElementById('wi-new-title')?.value?.trim();
371
371
  if (!title) { if (btn) { btn.disabled = false; btn.textContent = 'Create'; } alert('Title is required'); return; }
372
372
  const desc = document.getElementById('wi-new-desc')?.value || '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.366",
3
+ "version": "0.1.368",
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"