@yemi33/minions 0.1.146 β†’ 0.1.148

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,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.148 (2026-04-01)
4
+
5
+ ### Engine
6
+ - engine.js
7
+ - engine/lifecycle.js
8
+ - engine/shared.js
9
+
10
+ ### Dashboard
11
+ - dashboard/js/command-center.js
12
+ - dashboard/js/render-work-items.js
13
+ - dashboard/layout.html
14
+ - dashboard/styles.css
15
+
16
+ ### Other
17
+ - .github/workflows/pr-tests.yml
18
+ - test/unit.test.js
19
+
3
20
  ## 0.1.146 (2026-04-01)
4
21
 
5
22
  ### Engine
@@ -9,6 +9,7 @@ let _ccQueue = [];
9
9
  function toggleCommandCenter() {
10
10
  _ccOpen = !_ccOpen;
11
11
  const drawer = document.getElementById('cc-drawer');
12
+ if (_ccOpen) ccApplySavedWidth();
12
13
  drawer.style.display = _ccOpen ? 'flex' : 'none';
13
14
  if (_ccOpen) {
14
15
  clearNotifBadge(document.getElementById('cc-toggle-btn'));
@@ -462,4 +463,62 @@ async function ccExecuteAction(action) {
462
463
  refresh();
463
464
  }
464
465
 
466
+ // --- CC Resize Logic ---
467
+ const CC_MIN_WIDTH = 320;
468
+ const CC_MAX_WIDTH_RATIO = 0.8; // 80% of viewport
469
+ const CC_DEFAULT_WIDTH = 420;
470
+ const CC_WIDTH_KEY = 'cc-drawer-width';
471
+
472
+ function ccApplySavedWidth() {
473
+ const drawer = document.getElementById('cc-drawer');
474
+ if (!drawer) return;
475
+ const saved = parseInt(localStorage.getItem(CC_WIDTH_KEY), 10);
476
+ if (saved && saved >= CC_MIN_WIDTH) {
477
+ const maxW = Math.floor(window.innerWidth * CC_MAX_WIDTH_RATIO);
478
+ drawer.style.width = Math.min(saved, maxW) + 'px';
479
+ }
480
+ }
481
+
482
+ function ccInitResize() {
483
+ const handle = document.getElementById('cc-resize-handle');
484
+ const drawer = document.getElementById('cc-drawer');
485
+ if (!handle || !drawer) return;
486
+
487
+ let startX = 0;
488
+ let startW = 0;
489
+
490
+ function onMouseMove(e) {
491
+ const maxW = Math.floor(window.innerWidth * CC_MAX_WIDTH_RATIO);
492
+ const delta = startX - e.clientX; // dragging left = wider
493
+ const newW = Math.max(CC_MIN_WIDTH, Math.min(startW + delta, maxW));
494
+ drawer.style.width = newW + 'px';
495
+ }
496
+
497
+ function onMouseUp() {
498
+ document.removeEventListener('mousemove', onMouseMove);
499
+ document.removeEventListener('mouseup', onMouseUp);
500
+ document.body.classList.remove('cc-resizing');
501
+ handle.classList.remove('active');
502
+ // Persist
503
+ try { localStorage.setItem(CC_WIDTH_KEY, parseInt(drawer.style.width, 10)); } catch {}
504
+ }
505
+
506
+ handle.addEventListener('mousedown', (e) => {
507
+ e.preventDefault();
508
+ startX = e.clientX;
509
+ startW = drawer.offsetWidth;
510
+ document.body.classList.add('cc-resizing');
511
+ handle.classList.add('active');
512
+ document.addEventListener('mousemove', onMouseMove);
513
+ document.addEventListener('mouseup', onMouseUp);
514
+ });
515
+ }
516
+
517
+ // Init resize on DOM ready
518
+ if (document.readyState === 'loading') {
519
+ document.addEventListener('DOMContentLoaded', ccInitResize);
520
+ } else {
521
+ ccInitResize();
522
+ }
523
+
465
524
  window.MinionsCC = { toggleCommandCenter, ccNewSession, ccRestoreMessages, ccSaveState, ccUpdateSessionIndicator, ccAddMessage, ccSend, ccExecuteAction };
@@ -388,6 +388,9 @@ function openWorkItemDetail(id) {
388
388
  if (item.references?.length) html += field('References', item.references.map(r => '<a href="' + escHtml(r.url) + '" target="_blank" style="color:var(--blue)">' + escHtml(r.title || r.url) + '</a>' + (r.type ? ' <span style="color:var(--muted);font-size:10px">(' + escHtml(r.type) + ')</span>' : '')).join('<br>'));
389
389
  if (item._humanFeedback) html += field('Human Feedback', (item._humanFeedback.rating === 'up' ? 'πŸ‘' : 'πŸ‘Ž') + (item._humanFeedback.comment ? ' β€” ' + escHtml(item._humanFeedback.comment) : ''));
390
390
  if (item._pr) html += field('Pull Request', '<a href="' + escHtml(item._prUrl || '#') + '" target="_blank" style="color:var(--blue)">' + escHtml(item._pr) + '</a>');
391
+ if (item._totalCostUsd != null) html += field('Cumulative Cost', '$' + Number(item._totalCostUsd).toFixed(4));
392
+ if (item._totalInputTokens) html += field('Total Input Tokens', Number(item._totalInputTokens).toLocaleString());
393
+ if (item._totalOutputTokens) html += field('Total Output Tokens', Number(item._totalOutputTokens).toLocaleString());
391
394
  html += '</div>';
392
395
 
393
396
  document.getElementById('modal-title').textContent = item.title || item.id;
@@ -22,6 +22,7 @@
22
22
 
23
23
  <!-- Command Center Drawer -->
24
24
  <div id="cc-drawer" style="display:none;position:fixed;top:0;right:0;bottom:0;width:420px;background:var(--surface);border-left:1px solid var(--border);z-index:300;flex-direction:column">
25
+ <div id="cc-resize-handle" class="cc-resize-handle"></div>
25
26
  <div style="padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between">
26
27
  <div style="display:flex;align-items:center;gap:8px">
27
28
  <span style="font-weight:700;color:var(--blue);font-size:14px">Command Center</span>
@@ -581,6 +581,13 @@
581
581
  .notif-badge.processing span:nth-child(2) { animation-delay: 0.2s; }
582
582
  .notif-badge.processing span:nth-child(3) { animation-delay: 0.4s; }
583
583
  @keyframes notifPulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }
584
+
585
+ /* Command Center resize handle */
586
+ .cc-resize-handle { position: absolute; top: 0; left: -3px; bottom: 0; width: 6px; cursor: col-resize; z-index: 301; }
587
+ .cc-resize-handle::after { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 2px; height: 32px; background: var(--border); border-radius: 2px; opacity: 0; transition: opacity 0.15s; }
588
+ .cc-resize-handle:hover::after, .cc-resize-handle.active::after { opacity: 1; background: var(--blue); }
589
+ body.cc-resizing { cursor: col-resize !important; user-select: none !important; }
590
+
584
591
  .modal-body { padding: var(--space-7) var(--space-8); overflow-y: auto; overflow-x: auto; white-space: pre-wrap; font-size: var(--text-md); line-height: 1.7; color: var(--muted); font-family: Consolas, monospace; }
585
592
 
586
593
  /* Responsive: tablet / narrow window */
@@ -1153,6 +1153,47 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
1153
1153
  } catch (err) { log('warn', `Session save: ${err.message}`); }
1154
1154
  }
1155
1155
 
1156
+ // ── Accumulate per-work-item cost tracking ──────────────────────────────────
1157
+ if (taskUsage && meta?.item?.id) {
1158
+ try {
1159
+ const wiPath = meta.source === 'central-work-item' || meta.source === 'central-work-item-fanout'
1160
+ ? path.join(MINIONS_DIR, 'work-items.json')
1161
+ : meta.project?.name ? path.join(MINIONS_DIR, 'projects', meta.project.name, 'work-items.json') : null;
1162
+ if (wiPath) {
1163
+ mutateJsonFileLocked(wiPath, (items) => {
1164
+ if (!Array.isArray(items)) return items;
1165
+ const wi = items.find(i => i.id === meta.item.id);
1166
+ if (wi) {
1167
+ wi._totalCostUsd = (wi._totalCostUsd || 0) + (taskUsage.costUsd || 0);
1168
+ wi._totalInputTokens = (wi._totalInputTokens || 0) + (taskUsage.inputTokens || 0);
1169
+ wi._totalOutputTokens = (wi._totalOutputTokens || 0) + (taskUsage.outputTokens || 0);
1170
+ }
1171
+ return items;
1172
+ }, { defaultValue: [] });
1173
+
1174
+ // Cost ceiling circuit breaker β€” treat like evalMaxIterations exceeded
1175
+ const engineCfg = config?.engine || {};
1176
+ const evalMaxCost = engineCfg.evalMaxCost != null ? engineCfg.evalMaxCost : shared.ENGINE_DEFAULTS.evalMaxCost;
1177
+ if (evalMaxCost != null && evalMaxCost > 0) {
1178
+ const freshItems = safeJson(wiPath) || [];
1179
+ const wi = freshItems.find(i => i.id === meta.item.id);
1180
+ if (wi && wi._totalCostUsd > evalMaxCost && wi.status !== 'needs-human-review') {
1181
+ mutateJsonFileLocked(wiPath, (items) => {
1182
+ if (!Array.isArray(items)) return items;
1183
+ const target = items.find(i => i.id === meta.item.id);
1184
+ if (target) {
1185
+ target.status = 'needs-human-review';
1186
+ target.failReason = `Cumulative cost $${wi._totalCostUsd.toFixed(2)} exceeds evalMaxCost ceiling $${evalMaxCost.toFixed(2)}`;
1187
+ log('warn', `Work item ${meta.item.id} exceeded cost ceiling ($${wi._totalCostUsd.toFixed(2)} > $${evalMaxCost.toFixed(2)}) β€” needs-human-review`);
1188
+ }
1189
+ return items;
1190
+ }, { defaultValue: [] });
1191
+ }
1192
+ }
1193
+ }
1194
+ } catch (err) { log('warn', `Cost accumulation: ${err.message}`); }
1195
+ }
1196
+
1156
1197
  // Handle decomposition results β€” create sub-items from decompose agent output
1157
1198
  let skipDoneStatus = false;
1158
1199
  if (type === 'decompose' && isSuccess && meta?.item?.id) {
package/engine/shared.js CHANGED
@@ -358,6 +358,7 @@ const ENGINE_DEFAULTS = {
358
358
  meetingRoundTimeout: 600000, // 10min per meeting round before auto-advance
359
359
  evalLoop: true, // enable evaluateβ†’fix loop after implementation completes
360
360
  evalMaxIterations: 3, // max evaluateβ†’fix cycles before escalating to human
361
+ evalMaxCost: null, // USD ceiling per work item across all eval iterations; null = no limit (gather baseline data first)
361
362
  };
362
363
 
363
364
  const DEFAULT_AGENTS = {
package/engine.js CHANGED
@@ -1131,7 +1131,7 @@ function materializePlansAsWorkItems(config) {
1131
1131
 
1132
1132
  const id = item.id; // Work item ID = PRD item ID β€” no indirection
1133
1133
  const complexity = item.estimated_complexity || 'medium';
1134
- const criteria = (item.acceptance_criteria || []).map(c => `- ${c}`).join('\n');
1134
+ const criteria = (Array.isArray(item.acceptance_criteria) ? item.acceptance_criteria : []).map(c => `- ${c}`).join('\n');
1135
1135
 
1136
1136
  const newItem = {
1137
1137
  id,
@@ -1471,7 +1471,7 @@ function discoverFromWorkItems(config, project) {
1471
1471
  '- [' + (r.title || r.url) + '](' + r.url + ')' + (r.type ? ' (' + r.type + ')' : '')
1472
1472
  ).join('\n');
1473
1473
  vars.references = refs ? '## References\n\n' + refs : '';
1474
- const ac = (item.acceptanceCriteria || []).map(c => '- [ ] ' + c).join('\n');
1474
+ const ac = (Array.isArray(item.acceptanceCriteria) ? item.acceptanceCriteria : []).map(c => '- [ ] ' + c).join('\n');
1475
1475
  vars.acceptance_criteria = ac ? '## Acceptance Criteria\n\n' + ac : '';
1476
1476
 
1477
1477
  // Inject checkpoint context if agent left a checkpoint.json from a prior run
@@ -1806,7 +1806,7 @@ function discoverCentralWorkItems(config) {
1806
1806
  '- [' + (r.title || r.url) + '](' + r.url + ')' + (r.type ? ' (' + r.type + ')' : '')
1807
1807
  ).join('\n');
1808
1808
  vars.references = fanRefs ? '## References\n\n' + fanRefs : '';
1809
- const fanAc = (item.acceptanceCriteria || []).map(c => '- [ ] ' + c).join('\n');
1809
+ const fanAc = (Array.isArray(item.acceptanceCriteria) ? item.acceptanceCriteria : []).map(c => '- [ ] ' + c).join('\n');
1810
1810
  vars.acceptance_criteria = fanAc ? '## Acceptance Criteria\n\n' + fanAc : '';
1811
1811
 
1812
1812
  if (workType === 'ask') {
@@ -1888,7 +1888,7 @@ function discoverCentralWorkItems(config) {
1888
1888
  '- [' + (r.title || r.url) + '](' + r.url + ')' + (r.type ? ' (' + r.type + ')' : '')
1889
1889
  ).join('\n');
1890
1890
  vars.references = normRefs ? '## References\n\n' + normRefs : '';
1891
- const normAc = (item.acceptanceCriteria || []).map(c => '- [ ] ' + c).join('\n');
1891
+ const normAc = (Array.isArray(item.acceptanceCriteria) ? item.acceptanceCriteria : []).map(c => '- [ ] ' + c).join('\n');
1892
1892
  vars.acceptance_criteria = normAc ? '## Acceptance Criteria\n\n' + normAc : '';
1893
1893
 
1894
1894
  // Inject checkpoint context if agent left a checkpoint.json from a prior run
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.146",
3
+ "version": "0.1.148",
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"