@yemi33/minions 0.1.851 → 0.1.853

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,9 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.851 (2026-04-11)
3
+ ## 0.1.853 (2026-04-11)
4
4
 
5
5
  ### Features
6
+ - execute CC actions server-side (dispatch, note, knowledge)
6
7
  - per-type heartbeat timeouts for read-heavy work types (#652)
7
8
  - Replace raw status strings with WI_STATUS constants in dashboard.js (#657)
8
9
  - fix dispatch.json race condition in CLI kill-all (#653)
@@ -12,6 +13,7 @@
12
13
  - stale PRD shows Regenerate PRD + Resume as-is buttons
13
14
 
14
15
  ### Fixes
16
+ - 3 bugs in server-side CC action execution
15
17
  - PID file leak in indirect spawn + kb-sweep race condition (#821)
16
18
  - KB create modal stays open on API error for retry
17
19
  - steering resume failure no longer silently marked as SUCCESS
@@ -579,6 +579,7 @@ async function _ccDoSend(message, skipUserMsg) {
579
579
  ccSaveState(); ccUpdateSessionIndicator();
580
580
  }
581
581
  if (evt.actions && evt.actions.length > 0) {
582
+ _tagServerExecuted(evt.actions, evt.actionResults);
582
583
  for (var ai = 0; ai < evt.actions.length; ai++) { await ccExecuteAction(evt.actions[ai], activeTabId); }
583
584
  }
584
585
  } else if (evt.type === 'error') {
@@ -609,6 +610,7 @@ async function _ccDoSend(message, skipUserMsg) {
609
610
  ccSaveState(); ccUpdateSessionIndicator();
610
611
  }
611
612
  if (revt.actions && revt.actions.length > 0) {
613
+ _tagServerExecuted(revt.actions, revt.actionResults);
612
614
  for (var ai2 = 0; ai2 < revt.actions.length; ai2++) { await ccExecuteAction(revt.actions[ai2], activeTabId); }
613
615
  }
614
616
  } else if (revt.type === 'chunk') {
@@ -688,10 +690,42 @@ async function _ccFetch(url, body) {
688
690
  return res;
689
691
  }
690
692
 
693
+ // Tag actions that the server already executed so ccExecuteAction skips the API call
694
+ function _tagServerExecuted(actions, actionResults) {
695
+ if (!actionResults || !Array.isArray(actionResults)) return;
696
+ for (var i = 0; i < actions.length && i < actionResults.length; i++) {
697
+ var r = actionResults[i];
698
+ if (r && r.ok) {
699
+ actions[i]._serverExecuted = true;
700
+ if (r.id) actions[i]._serverId = r.id;
701
+ } else if (r && r.error) {
702
+ actions[i]._serverExecuted = true;
703
+ actions[i]._serverError = r.error;
704
+ }
705
+ // clientExecuted: false means server didn't handle it — frontend must execute
706
+ }
707
+ }
708
+
691
709
  async function ccExecuteAction(action, targetTabId) {
692
710
  var status = document.createElement('div');
693
711
  status.style.cssText = 'padding:4px 10px;border-radius:4px;font-size:10px;align-self:flex-start;border:1px dashed var(--border);color:var(--muted)';
694
712
 
713
+ // Server-executed actions: just show status, don't re-fire the API
714
+ if (action._serverExecuted) {
715
+ if (action._serverError) {
716
+ status.innerHTML = '&#10007; ' + escHtml(action.type) + ' failed: ' + escHtml(action._serverError);
717
+ status.style.color = 'var(--red)';
718
+ } else {
719
+ var label = action._serverId ? escHtml(action._serverId) : escHtml(action.title || action.type);
720
+ status.innerHTML = '&#10003; ' + escHtml(action.type) + ': <strong>' + label + '</strong>';
721
+ status.style.color = 'var(--green)';
722
+ }
723
+ ccAddMessage('assistant', status.outerHTML, false, targetTabId);
724
+ if (['dispatch','fix','implement','explore','review','test'].includes(action.type)) wakeEngine();
725
+ refresh();
726
+ return;
727
+ }
728
+
695
729
  try {
696
730
  switch (action.type) {
697
731
  case 'dispatch':
package/dashboard.js CHANGED
@@ -572,6 +572,63 @@ function parseCCActions(text) {
572
572
  return { text: displayText, actions };
573
573
  }
574
574
 
575
+ // ── Server-side CC action execution ──────────────────────────────────────────
576
+ // Actions are executed server-side so all clients (frontend, curl, Teams) get the same behavior.
577
+ // The frontend still shows status toasts but no longer needs to fire the API calls.
578
+
579
+ async function executeCCActions(actions) {
580
+ const results = [];
581
+ for (const action of actions) {
582
+ try {
583
+ switch (action.type) {
584
+ case 'dispatch': case 'fix': case 'implement': case 'explore': case 'review': case 'test': {
585
+ const workType = action.workType || (action.type !== 'dispatch' ? action.type : 'implement');
586
+ const id = 'W-' + shared.uid();
587
+ const project = action.project || '';
588
+ const targetProject = project ? PROJECTS.find(p => p.name?.toLowerCase() === project.toLowerCase()) : PROJECTS[0];
589
+ const wiPath = targetProject ? shared.projectWorkItemsPath(targetProject) : path.join(MINIONS_DIR, 'work-items.json');
590
+ shared.mutateJsonFileLocked(wiPath, items => {
591
+ if (!Array.isArray(items)) items = [];
592
+ items.push({
593
+ id, title: action.title || 'Untitled', type: workType,
594
+ priority: action.priority || 'medium', description: action.description || '',
595
+ status: WI_STATUS.PENDING, created: new Date().toISOString(),
596
+ createdBy: 'command-center', project,
597
+ ...(action.agents?.length ? { preferred_agent: action.agents[0] } : {}),
598
+ });
599
+ return items;
600
+ }, { defaultValue: [] });
601
+ results.push({ type: action.type, id, ok: true });
602
+ break;
603
+ }
604
+ case 'note': {
605
+ shared.writeToInbox('command-center', shared.slugify(action.title || 'note'), `# ${action.title || 'Note'}\n\n${action.content || action.description || ''}`);
606
+ results.push({ type: 'note', ok: true });
607
+ break;
608
+ }
609
+ case 'knowledge': {
610
+ const validCategories = ['architecture', 'conventions', 'project-notes', 'build-reports', 'reviews'];
611
+ const category = action.category || 'project-notes';
612
+ if (!validCategories.includes(category)) { results.push({ type: 'knowledge', error: 'Invalid category: ' + category }); break; }
613
+ const slug = shared.slugify(action.title || 'entry');
614
+ const kbDir = path.join(MINIONS_DIR, 'knowledge', category);
615
+ if (!fs.existsSync(kbDir)) fs.mkdirSync(kbDir, { recursive: true });
616
+ shared.safeWrite(path.join(kbDir, slug + '.md'), `# ${action.title}\n\n${action.content || action.description || ''}`);
617
+ results.push({ type: 'knowledge', ok: true });
618
+ break;
619
+ }
620
+ default:
621
+ // Server didn't handle — frontend must execute
622
+ results.push({ type: action.type });
623
+ break;
624
+ }
625
+ } catch (e) {
626
+ results.push({ type: action.type, error: e.message });
627
+ }
628
+ }
629
+ return results;
630
+ }
631
+
575
632
  // ── Shared LLM call core — used by CC panel and doc modals ──────────────────
576
633
 
577
634
  // Session store for doc modals — keyed by filePath or title, persisted to disk
@@ -3424,7 +3481,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3424
3481
  teams.teamsPostCCResponse(body.message, result.text).catch(() => {});
3425
3482
  }
3426
3483
 
3427
- const reply = { ...parseCCActions(result.text), sessionId: ccSession.sessionId, newSession: !wasResume };
3484
+ const parsed = parseCCActions(result.text);
3485
+ if (parsed.actions.length > 0) {
3486
+ parsed.actionResults = await executeCCActions(parsed.actions);
3487
+ }
3488
+ const reply = { ...parsed, sessionId: ccSession.sessionId, newSession: !wasResume };
3428
3489
  if (sessionReset) reply.sessionReset = true;
3429
3490
  return jsonReply(res, 200, reply);
3430
3491
  } finally {
@@ -3534,9 +3595,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3534
3595
  } catch { /* non-critical */ }
3535
3596
  }
3536
3597
 
3537
- // Send final result with actions
3598
+ // Send final result with actions — execute server-side first
3538
3599
  const { text: displayText, actions } = parseCCActions(result.text);
3539
- const donePayload = { type: 'done', text: displayText, actions, sessionId: responseSessionId, newSession: !wasResume };
3600
+ let actionResults;
3601
+ if (actions.length > 0) {
3602
+ actionResults = await executeCCActions(actions);
3603
+ }
3604
+ const donePayload = { type: 'done', text: displayText, actions, actionResults, sessionId: responseSessionId, newSession: !wasResume };
3540
3605
  if (sessionReset) donePayload.sessionReset = true;
3541
3606
  res.write('data: ' + JSON.stringify(donePayload) + '\n\n');
3542
3607
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.851",
3
+ "version": "0.1.853",
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"