@yemi33/minions 0.1.850 → 0.1.852
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 +3 -1
- package/dashboard/js/command-center.js +35 -0
- package/dashboard.js +66 -3
- package/engine/shared.js +10 -0
- package/engine/timeout.js +6 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.852 (2026-04-11)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- execute CC actions server-side (dispatch, note, knowledge)
|
|
7
|
+
- per-type heartbeat timeouts for read-heavy work types (#652)
|
|
6
8
|
- Replace raw status strings with WI_STATUS constants in dashboard.js (#657)
|
|
7
9
|
- fix dispatch.json race condition in CLI kill-all (#653)
|
|
8
10
|
- optimistic UI updates for all remaining dashboard pages
|
|
@@ -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,43 @@ 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
|
+
|
|
709
|
+
|
|
691
710
|
async function ccExecuteAction(action, targetTabId) {
|
|
692
711
|
var status = document.createElement('div');
|
|
693
712
|
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
713
|
|
|
714
|
+
// Server-executed actions: just show status, don't re-fire the API
|
|
715
|
+
if (action._serverExecuted) {
|
|
716
|
+
if (action._serverError) {
|
|
717
|
+
status.innerHTML = '✗ ' + escHtml(action.type) + ' failed: ' + escHtml(action._serverError);
|
|
718
|
+
status.style.color = 'var(--red)';
|
|
719
|
+
} else {
|
|
720
|
+
var label = action._serverId ? escHtml(action._serverId) : escHtml(action.title || action.type);
|
|
721
|
+
status.innerHTML = '✓ ' + escHtml(action.type) + ': <strong>' + label + '</strong>';
|
|
722
|
+
status.style.color = 'var(--green)';
|
|
723
|
+
}
|
|
724
|
+
ccAddMessage('assistant', status.outerHTML, false, targetTabId);
|
|
725
|
+
if (['dispatch','fix','implement','explore','review','test'].includes(action.type)) wakeEngine();
|
|
726
|
+
refresh();
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
|
|
695
730
|
try {
|
|
696
731
|
switch (action.type) {
|
|
697
732
|
case 'dispatch':
|
package/dashboard.js
CHANGED
|
@@ -572,6 +572,61 @@ 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: '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 category = action.category || 'project-notes';
|
|
611
|
+
const slug = shared.slugify(action.title || 'entry');
|
|
612
|
+
const kbDir = path.join(MINIONS_DIR, 'knowledge', category);
|
|
613
|
+
if (!fs.existsSync(kbDir)) fs.mkdirSync(kbDir, { recursive: true });
|
|
614
|
+
shared.safeWrite(path.join(kbDir, slug + '.md'), `# ${action.title}\n\n${action.content || action.description || ''}`);
|
|
615
|
+
results.push({ type: 'knowledge', ok: true });
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
default:
|
|
619
|
+
// For action types that map 1:1 to API endpoints, flag as client-executed
|
|
620
|
+
results.push({ type: action.type, ok: true, clientExecuted: false });
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
} catch (e) {
|
|
624
|
+
results.push({ type: action.type, error: e.message });
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return results;
|
|
628
|
+
}
|
|
629
|
+
|
|
575
630
|
// ── Shared LLM call core — used by CC panel and doc modals ──────────────────
|
|
576
631
|
|
|
577
632
|
// Session store for doc modals — keyed by filePath or title, persisted to disk
|
|
@@ -3424,7 +3479,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3424
3479
|
teams.teamsPostCCResponse(body.message, result.text).catch(() => {});
|
|
3425
3480
|
}
|
|
3426
3481
|
|
|
3427
|
-
const
|
|
3482
|
+
const parsed = parseCCActions(result.text);
|
|
3483
|
+
if (parsed.actions.length > 0) {
|
|
3484
|
+
parsed.actionResults = await executeCCActions(parsed.actions);
|
|
3485
|
+
}
|
|
3486
|
+
const reply = { ...parsed, sessionId: ccSession.sessionId, newSession: !wasResume };
|
|
3428
3487
|
if (sessionReset) reply.sessionReset = true;
|
|
3429
3488
|
return jsonReply(res, 200, reply);
|
|
3430
3489
|
} finally {
|
|
@@ -3534,9 +3593,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3534
3593
|
} catch { /* non-critical */ }
|
|
3535
3594
|
}
|
|
3536
3595
|
|
|
3537
|
-
// Send final result with actions
|
|
3596
|
+
// Send final result with actions — execute server-side first
|
|
3538
3597
|
const { text: displayText, actions } = parseCCActions(result.text);
|
|
3539
|
-
|
|
3598
|
+
let actionResults;
|
|
3599
|
+
if (actions.length > 0) {
|
|
3600
|
+
actionResults = await executeCCActions(actions);
|
|
3601
|
+
}
|
|
3602
|
+
const donePayload = { type: 'done', text: displayText, actions, actionResults, sessionId: responseSessionId, newSession: !wasResume };
|
|
3540
3603
|
if (sessionReset) donePayload.sessionReset = true;
|
|
3541
3604
|
res.write('data: ' + JSON.stringify(donePayload) + '\n\n');
|
|
3542
3605
|
|
package/engine/shared.js
CHANGED
|
@@ -552,6 +552,7 @@ const ENGINE_DEFAULTS = {
|
|
|
552
552
|
ignoredCommentAuthors: [], // comments from these authors are auto-closed and never trigger fixes
|
|
553
553
|
ccModel: 'sonnet', // model for Command Center and doc-chat (sonnet, haiku, opus)
|
|
554
554
|
ccEffort: null, // effort level for CC/doc-chat (null, 'low', 'medium', 'high')
|
|
555
|
+
heartbeatTimeouts: {}, // populated after WORK_TYPE is defined (below)
|
|
555
556
|
ccMaxTurns: 50, // max tool-use turns for CC/doc-chat before CLI stops
|
|
556
557
|
// Teams integration — config.teams shape: { enabled, appId, appPassword, notifyEvents, ccMirror, inboxPollInterval }
|
|
557
558
|
teams: {
|
|
@@ -582,6 +583,15 @@ const WORK_TYPE = {
|
|
|
582
583
|
VERIFY: 'verify', PLAN: 'plan', PLAN_TO_PRD: 'plan-to-prd', DECOMPOSE: 'decompose',
|
|
583
584
|
MEETING: 'meeting', EXPLORE: 'explore', ASK: 'ask', TEST: 'test', DOCS: 'docs',
|
|
584
585
|
};
|
|
586
|
+
|
|
587
|
+
// Per-work-type heartbeat timeouts (ms) — read-heavy tasks need longer silence windows.
|
|
588
|
+
// Keyed by WORK_TYPE constants; types not listed fall back to ENGINE_DEFAULTS.heartbeatTimeout.
|
|
589
|
+
Object.assign(ENGINE_DEFAULTS.heartbeatTimeouts, {
|
|
590
|
+
[WORK_TYPE.EXPLORE]: 600000, // 10 min — spends most time reading/analyzing, minimal stdout
|
|
591
|
+
[WORK_TYPE.ASK]: 600000, // 10 min — research-heavy, long silent analysis periods
|
|
592
|
+
[WORK_TYPE.REVIEW]: 480000, // 8 min — code review reads extensively before producing output
|
|
593
|
+
});
|
|
594
|
+
|
|
585
595
|
const PLAN_STATUS = {
|
|
586
596
|
ACTIVE: 'active', AWAITING_APPROVAL: 'awaiting-approval', APPROVED: 'approved',
|
|
587
597
|
PAUSED: 'paused', REJECTED: 'rejected', COMPLETED: 'completed',
|
package/engine/timeout.js
CHANGED
|
@@ -9,7 +9,7 @@ const shared = require('./shared');
|
|
|
9
9
|
const queries = require('./queries');
|
|
10
10
|
|
|
11
11
|
const { safeRead, safeWrite, safeJson, mutateJsonFileLocked, getProjects, projectWorkItemsPath, log, ts,
|
|
12
|
-
ENGINE_DEFAULTS: DEFAULTS, WI_STATUS, DISPATCH_RESULT, AGENT_STATUS } = shared;
|
|
12
|
+
ENGINE_DEFAULTS: DEFAULTS, WI_STATUS, WORK_TYPE, DISPATCH_RESULT, AGENT_STATUS } = shared;
|
|
13
13
|
const { getDispatch, getAgentStatus } = queries;
|
|
14
14
|
const AGENTS_DIR = queries.AGENTS_DIR;
|
|
15
15
|
const MINIONS_DIR = shared.MINIONS_DIR;
|
|
@@ -130,7 +130,7 @@ function checkTimeouts(config) {
|
|
|
130
130
|
const { runPostCompletionHooks } = require('./lifecycle');
|
|
131
131
|
|
|
132
132
|
const timeout = config.engine?.agentTimeout || DEFAULTS.agentTimeout;
|
|
133
|
-
const
|
|
133
|
+
const defaultHeartbeatTimeout = config.engine?.heartbeatTimeout || DEFAULTS.heartbeatTimeout;
|
|
134
134
|
|
|
135
135
|
// Per-type heartbeat timeouts: merge ENGINE_DEFAULTS ← config overrides
|
|
136
136
|
const perTypeTimeouts = { ...DEFAULTS.heartbeatTimeouts, ...(config.engine?.heartbeatTimeouts || {}) };
|
|
@@ -155,6 +155,10 @@ function checkTimeouts(config) {
|
|
|
155
155
|
for (const item of (dispatchData.active || [])) {
|
|
156
156
|
if (!item.agent) continue;
|
|
157
157
|
|
|
158
|
+
// Per-type heartbeat: look up work type from dispatch item, fall back to default
|
|
159
|
+
const workType = item.workType || item.meta?.item?.type;
|
|
160
|
+
const heartbeatTimeout = (workType && perTypeTimeouts[workType]) || defaultHeartbeatTimeout;
|
|
161
|
+
|
|
158
162
|
const hasProcess = activeProcesses.has(item.id);
|
|
159
163
|
const liveLogPath = path.join(AGENTS_DIR, item.agent, 'live-output.log');
|
|
160
164
|
let lastActivity = item.started_at ? new Date(item.started_at).getTime() : 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.852",
|
|
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"
|