@yemi33/minions 0.1.317 → 0.1.319
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 +7 -1
- package/dashboard/js/render-other.js +1 -1
- package/dashboard.js +1 -1
- package/engine.js +44 -44
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.319 (2026-04-03)
|
|
4
|
+
|
|
5
|
+
### Other
|
|
6
|
+
- refactor: replace magic strings in engine.js with constants
|
|
7
|
+
|
|
8
|
+
## 0.1.318 (2026-04-03)
|
|
4
9
|
|
|
5
10
|
### Fixes
|
|
11
|
+
- scan modal shows actual home directory instead of ~
|
|
6
12
|
- deduplicate PRs in pull-requests.json on write
|
|
7
13
|
- show reviewer names in dashboard Signed Off By column
|
|
8
14
|
|
|
@@ -249,7 +249,7 @@ async function openScanProjectsModal() {
|
|
|
249
249
|
'<div style="display:flex;flex-direction:column;gap:12px">' +
|
|
250
250
|
'<div style="display:flex;gap:8px;align-items:flex-end">' +
|
|
251
251
|
'<label style="flex:1;color:var(--text);font-size:var(--text-md)">Directory to scan' +
|
|
252
|
-
'<input id="scan-path" value="' + escHtml(
|
|
252
|
+
'<input id="scan-path" value="' + escHtml(window.__MINIONS_HOME || '~') + '" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md)">' +
|
|
253
253
|
'</label>' +
|
|
254
254
|
'<label style="width:60px;color:var(--text);font-size:var(--text-md)">Depth' +
|
|
255
255
|
'<input id="scan-depth" type="number" value="3" min="1" max="6" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md)">' +
|
package/dashboard.js
CHANGED
|
@@ -94,7 +94,7 @@ function buildDashboardHtml() {
|
|
|
94
94
|
return layout
|
|
95
95
|
.replace('/* __CSS__ */', () => css)
|
|
96
96
|
.replace('<!-- __PAGES__ -->', () => pageHtml)
|
|
97
|
-
.replace('/* __JS__ */', () => jsHtml);
|
|
97
|
+
.replace('/* __JS__ */', () => `window.__MINIONS_HOME = ${JSON.stringify(os.homedir())};\n${jsHtml}`);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
let HTML_RAW = buildDashboardHtml();
|
package/engine.js
CHANGED
|
@@ -396,7 +396,7 @@ function spawnAgent(dispatchItem, config) {
|
|
|
396
396
|
log('warn', `Proceeding with recovered worktree after add failure for ${branchName}`);
|
|
397
397
|
} else {
|
|
398
398
|
log('error', `Failed to create worktree for ${branchName}: ${err.message}${err.stderr ? '\n' + err.stderr.toString().slice(0, 500) : ''}`);
|
|
399
|
-
completeDispatch(id,
|
|
399
|
+
completeDispatch(id, DISPATCH_RESULT.ERROR, 'Worktree creation failed: ' + (err.message || '').slice(0, 200));
|
|
400
400
|
return null;
|
|
401
401
|
}
|
|
402
402
|
}
|
|
@@ -610,7 +610,7 @@ function spawnAgent(dispatchItem, config) {
|
|
|
610
610
|
resumeProc.on('error', (err) => {
|
|
611
611
|
log('error', `Steering re-spawn failed for ${agentId}: ${err.message}`);
|
|
612
612
|
activeProcesses.delete(id);
|
|
613
|
-
completeDispatch(id,
|
|
613
|
+
completeDispatch(id, DISPATCH_RESULT.ERROR, `Steering re-spawn error: ${err.message}`);
|
|
614
614
|
});
|
|
615
615
|
|
|
616
616
|
// Don't run completion hooks — agent is still working
|
|
@@ -642,7 +642,7 @@ function spawnAgent(dispatchItem, config) {
|
|
|
642
642
|
if (code === 78) {
|
|
643
643
|
const errMsg = stderr.includes('claude-code') ? stderr.trim() : 'Configuration error — Claude Code CLI not found. Install with: npm install -g @anthropic-ai/claude-code';
|
|
644
644
|
log('error', `Agent ${agentId} (${id}) failed: ${errMsg}`);
|
|
645
|
-
completeDispatch(id,
|
|
645
|
+
completeDispatch(id, DISPATCH_RESULT.ERROR, errMsg, '');
|
|
646
646
|
try { fs.unlinkSync(sysPromptPath); } catch { /* cleanup */ }
|
|
647
647
|
try { fs.unlinkSync(promptPath); } catch { /* cleanup */ }
|
|
648
648
|
try { fs.unlinkSync(promptPath.replace(/prompt-/, 'pid-').replace(/\.md$/, '.pid')); } catch { /* cleanup */ }
|
|
@@ -653,7 +653,7 @@ function spawnAgent(dispatchItem, config) {
|
|
|
653
653
|
const { resultSummary } = runPostCompletionHooks(dispatchItem, agentId, code, stdout, config);
|
|
654
654
|
|
|
655
655
|
// Move from active to completed in dispatch (single source of truth for agent status)
|
|
656
|
-
completeDispatch(id, code === 0 ?
|
|
656
|
+
completeDispatch(id, code === 0 ? DISPATCH_RESULT.SUCCESS : DISPATCH_RESULT.ERROR, '', resultSummary);
|
|
657
657
|
|
|
658
658
|
// Cleanup temp files (including PID file now that dispatch is complete)
|
|
659
659
|
try { fs.unlinkSync(sysPromptPath); } catch { /* cleanup */ }
|
|
@@ -680,7 +680,7 @@ function spawnAgent(dispatchItem, config) {
|
|
|
680
680
|
if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; }
|
|
681
681
|
log('error', `Failed to spawn agent ${agentId}: ${err.message}`);
|
|
682
682
|
activeProcesses.delete(id);
|
|
683
|
-
completeDispatch(id,
|
|
683
|
+
completeDispatch(id, DISPATCH_RESULT.ERROR, `Spawn error: ${err.message}`);
|
|
684
684
|
});
|
|
685
685
|
|
|
686
686
|
// Safety: if process exits immediately (within 3s), log it
|
|
@@ -769,7 +769,7 @@ function areDependenciesMet(item, config) {
|
|
|
769
769
|
log('warn', `Dependency ${depId} not found for ${item.id} (plan: ${sourcePlan}) — treating as unmet`);
|
|
770
770
|
return false;
|
|
771
771
|
}
|
|
772
|
-
if (depItem.status ===
|
|
772
|
+
if (depItem.status === WI_STATUS.FAILED) return 'failed';
|
|
773
773
|
if (!PRD_MET_STATUSES.has(depItem.status)) return false; // Pending, dispatched, or retrying — wait (legacy aliases accepted)
|
|
774
774
|
}
|
|
775
775
|
return true;
|
|
@@ -801,7 +801,7 @@ function reconcileItemsWithPrs(items, allPrs, { onlyIds } = {}) {
|
|
|
801
801
|
const prLinks = shared.getPrLinks();
|
|
802
802
|
let reconciled = 0;
|
|
803
803
|
for (const wi of items) {
|
|
804
|
-
if (wi.status !==
|
|
804
|
+
if (wi.status !== WI_STATUS.PENDING || wi._pr) continue;
|
|
805
805
|
if (onlyIds && !onlyIds.has(wi.id)) continue;
|
|
806
806
|
|
|
807
807
|
let exactPr = allPrs.find(pr => (pr.prdItems || []).includes(wi.id));
|
|
@@ -810,7 +810,7 @@ function reconcileItemsWithPrs(items, allPrs, { onlyIds } = {}) {
|
|
|
810
810
|
if (linkedPrId) exactPr = allPrs.find(pr => pr.id === linkedPrId) || { id: linkedPrId };
|
|
811
811
|
}
|
|
812
812
|
if (exactPr) {
|
|
813
|
-
wi.status =
|
|
813
|
+
wi.status = WI_STATUS.DONE;
|
|
814
814
|
wi._pr = exactPr.id;
|
|
815
815
|
reconciled++;
|
|
816
816
|
}
|
|
@@ -894,7 +894,7 @@ function autoCleanPrdWorkItems(prdFile, config) {
|
|
|
894
894
|
const items = safeJson(wiPath);
|
|
895
895
|
if (!items) continue;
|
|
896
896
|
const filtered = items.filter(w => {
|
|
897
|
-
if (w.sourcePlan === prdFile && (w.status ===
|
|
897
|
+
if (w.sourcePlan === prdFile && (w.status === WI_STATUS.PENDING || w.status === WI_STATUS.FAILED)) {
|
|
898
898
|
deletedIds.push(w.id); return false;
|
|
899
899
|
}
|
|
900
900
|
return true;
|
|
@@ -1011,7 +1011,7 @@ function materializePlansAsWorkItems(config) {
|
|
|
1011
1011
|
const centralWiPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
1012
1012
|
const centralItems = safeJson(centralWiPath) || [];
|
|
1013
1013
|
const alreadyQueued = centralItems.some(w =>
|
|
1014
|
-
w.type ===
|
|
1014
|
+
w.type === WORK_TYPE.PLAN_TO_PRD && w.planFile === plan.source_plan && (w.status === WI_STATUS.PENDING || w.status === WI_STATUS.DISPATCHED)
|
|
1015
1015
|
);
|
|
1016
1016
|
if (!alreadyQueued) {
|
|
1017
1017
|
centralItems.push({
|
|
@@ -1054,7 +1054,7 @@ function materializePlansAsWorkItems(config) {
|
|
|
1054
1054
|
continue; // Skip — waiting for human approval
|
|
1055
1055
|
}
|
|
1056
1056
|
}
|
|
1057
|
-
if (planStatus ===
|
|
1057
|
+
if (planStatus === PLAN_STATUS.PAUSED || planStatus === PLAN_STATUS.REJECTED || planStatus === PLAN_STATUS.REVISION_REQUESTED) {
|
|
1058
1058
|
continue; // Skip — paused or revision requested
|
|
1059
1059
|
}
|
|
1060
1060
|
// Stale PRDs: source plan was revised — don't materialize NEW items until user regenerates
|
|
@@ -1082,7 +1082,7 @@ function materializePlansAsWorkItems(config) {
|
|
|
1082
1082
|
}
|
|
1083
1083
|
const items = plan.missing_features.filter(f =>
|
|
1084
1084
|
statusFilter.includes(f.status) ||
|
|
1085
|
-
((f.status
|
|
1085
|
+
(DONE_STATUSES.has(f.status) && f.id && !allExistingWiIds.has(f.id))
|
|
1086
1086
|
);
|
|
1087
1087
|
|
|
1088
1088
|
// Group items by target project (per-item project field overrides plan-level project)
|
|
@@ -1175,7 +1175,7 @@ function materializePlansAsWorkItems(config) {
|
|
|
1175
1175
|
const currentPrdIds = new Set(plan.missing_features.map(f => f.id));
|
|
1176
1176
|
let cancelled = 0;
|
|
1177
1177
|
for (const wi of existingItems) {
|
|
1178
|
-
if (wi.status !==
|
|
1178
|
+
if (wi.status !== WI_STATUS.PENDING || wi.sourcePlan !== file) continue;
|
|
1179
1179
|
if (!currentPrdIds.has(wi.id)) {
|
|
1180
1180
|
wi.status = 'cancelled';
|
|
1181
1181
|
wi.cancelledAt = ts();
|
|
@@ -1376,23 +1376,23 @@ function discoverFromWorkItems(config, project) {
|
|
|
1376
1376
|
for (const item of items) {
|
|
1377
1377
|
try {
|
|
1378
1378
|
// Re-evaluate failed items: if deps have recovered, reset to pending
|
|
1379
|
-
if (item.status ===
|
|
1379
|
+
if (item.status === WI_STATUS.FAILED && item.failReason === 'Dependency failed — cannot proceed') {
|
|
1380
1380
|
const depStatus = areDependenciesMet(item, config);
|
|
1381
1381
|
if (depStatus === true) {
|
|
1382
|
-
item.status =
|
|
1382
|
+
item.status = WI_STATUS.PENDING;
|
|
1383
1383
|
delete item.failReason;
|
|
1384
1384
|
log('info', `Recovered ${item.id} from dependency failure — deps now met`);
|
|
1385
1385
|
needsWrite = true;
|
|
1386
1386
|
}
|
|
1387
1387
|
}
|
|
1388
1388
|
|
|
1389
|
-
if (item.status !==
|
|
1389
|
+
if (item.status !== WI_STATUS.QUEUED && item.status !== WI_STATUS.PENDING) continue;
|
|
1390
1390
|
|
|
1391
1391
|
// Dependency gate: skip items whose depends_on are not yet met; propagate failure
|
|
1392
1392
|
if (item.depends_on && item.depends_on.length > 0) {
|
|
1393
1393
|
const depStatus = areDependenciesMet(item, config);
|
|
1394
1394
|
if (depStatus === 'failed') {
|
|
1395
|
-
item.status =
|
|
1395
|
+
item.status = WI_STATUS.FAILED;
|
|
1396
1396
|
item.failReason = 'Dependency failed — cannot proceed';
|
|
1397
1397
|
delete item._pendingReason;
|
|
1398
1398
|
log('warn', `Marking ${item.id} as failed: dependency failed (plan: ${item.sourcePlan})`);
|
|
@@ -1422,7 +1422,7 @@ function discoverFromWorkItems(config, project) {
|
|
|
1422
1422
|
safeWrite(projectWorkItemsPath(project), items);
|
|
1423
1423
|
}
|
|
1424
1424
|
if (isAlreadyDispatched(key)) {
|
|
1425
|
-
if (item.status ===
|
|
1425
|
+
if (item.status === WI_STATUS.PENDING) { item.status = WI_STATUS.DISPATCHED; needsWrite = true; }
|
|
1426
1426
|
if (item._pendingReason !== 'already_dispatched') { item._pendingReason = 'already_dispatched'; needsWrite = true; }
|
|
1427
1427
|
skipped.gated++; continue;
|
|
1428
1428
|
}
|
|
@@ -1432,12 +1432,12 @@ function discoverFromWorkItems(config, project) {
|
|
|
1432
1432
|
}
|
|
1433
1433
|
|
|
1434
1434
|
let workType = item.type || 'implement';
|
|
1435
|
-
if (workType ===
|
|
1436
|
-
workType =
|
|
1435
|
+
if (workType === WORK_TYPE.IMPLEMENT && (item.complexity === 'large' || item.estimated_complexity === 'large')) {
|
|
1436
|
+
workType = WORK_TYPE.IMPLEMENT_LARGE;
|
|
1437
1437
|
}
|
|
1438
1438
|
// Auto-decompose large items before implementation
|
|
1439
1439
|
if (workType === 'implement:large' && !item._decomposed && !item._decomposing && config.engine?.autoDecompose !== false) {
|
|
1440
|
-
workType =
|
|
1440
|
+
workType = WORK_TYPE.DECOMPOSE;
|
|
1441
1441
|
item._decomposing = true;
|
|
1442
1442
|
needsWrite = true;
|
|
1443
1443
|
}
|
|
@@ -1522,7 +1522,7 @@ function discoverFromWorkItems(config, project) {
|
|
|
1522
1522
|
} catch (e) { log('warn', `checkpoint read for ${item.id}: ${e.message}`); }
|
|
1523
1523
|
|
|
1524
1524
|
// Inject ask-specific variables for the ask playbook
|
|
1525
|
-
if (workType ===
|
|
1525
|
+
if (workType === WORK_TYPE.ASK) {
|
|
1526
1526
|
vars.question = item.title + (item.description ? '\n\n' + item.description : '');
|
|
1527
1527
|
vars.task_id = item.id;
|
|
1528
1528
|
vars.notes_content = '';
|
|
@@ -1537,7 +1537,7 @@ function discoverFromWorkItems(config, project) {
|
|
|
1537
1537
|
}
|
|
1538
1538
|
|
|
1539
1539
|
const playbookName = selectPlaybook(workType, item);
|
|
1540
|
-
if (playbookName === 'work-item' && workType ===
|
|
1540
|
+
if (playbookName === 'work-item' && workType === WORK_TYPE.REVIEW) {
|
|
1541
1541
|
log('info', `Work item ${item.id} is type "review" but has no PR — using work-item playbook`);
|
|
1542
1542
|
}
|
|
1543
1543
|
const prompt = item.prompt || renderPlaybook(playbookName, vars) || renderPlaybook('work-item', vars) || item.description;
|
|
@@ -1547,7 +1547,7 @@ function discoverFromWorkItems(config, project) {
|
|
|
1547
1547
|
}
|
|
1548
1548
|
|
|
1549
1549
|
// Mark item as dispatched BEFORE adding to newWork (prevents race on next tick)
|
|
1550
|
-
item.status =
|
|
1550
|
+
item.status = WI_STATUS.DISPATCHED;
|
|
1551
1551
|
item.dispatched_at = ts();
|
|
1552
1552
|
item.dispatched_to = agentId;
|
|
1553
1553
|
delete item._pendingReason;
|
|
@@ -1772,7 +1772,7 @@ function discoverCentralWorkItems(config) {
|
|
|
1772
1772
|
|
|
1773
1773
|
for (const item of items) {
|
|
1774
1774
|
try {
|
|
1775
|
-
if (item.status !==
|
|
1775
|
+
if (item.status !== WI_STATUS.QUEUED && item.status !== WI_STATUS.PENDING) continue;
|
|
1776
1776
|
|
|
1777
1777
|
const key = `central-work-${item.id}`;
|
|
1778
1778
|
if (isAlreadyDispatched(key) || isOnCooldown(key, 0)) continue;
|
|
@@ -1824,7 +1824,7 @@ function discoverCentralWorkItems(config) {
|
|
|
1824
1824
|
const fanAc = normalizeAc(item.acceptanceCriteria).map(c => '- [ ] ' + c).join('\n');
|
|
1825
1825
|
vars.acceptance_criteria = fanAc ? '## Acceptance Criteria\n\n' + fanAc : '';
|
|
1826
1826
|
|
|
1827
|
-
if (workType ===
|
|
1827
|
+
if (workType === WORK_TYPE.ASK) {
|
|
1828
1828
|
vars.question = item.title + (item.description ? '\n\n' + item.description : '');
|
|
1829
1829
|
vars.task_id = item.id;
|
|
1830
1830
|
vars.notes_content = '';
|
|
@@ -1858,7 +1858,7 @@ function discoverCentralWorkItems(config) {
|
|
|
1858
1858
|
});
|
|
1859
1859
|
}
|
|
1860
1860
|
|
|
1861
|
-
item.status =
|
|
1861
|
+
item.status = WI_STATUS.DISPATCHED;
|
|
1862
1862
|
item.dispatched_at = ts();
|
|
1863
1863
|
item.dispatched_to = idleAgents.map(a => a.id).join(', ');
|
|
1864
1864
|
item.scope = 'fan-out';
|
|
@@ -1934,7 +1934,7 @@ function discoverCentralWorkItems(config) {
|
|
|
1934
1934
|
} catch (e) { log('warn', `checkpoint read for ${item.id}: ${e.message}`); }
|
|
1935
1935
|
|
|
1936
1936
|
// Inject plan-specific variables for the plan playbook
|
|
1937
|
-
if (workType ===
|
|
1937
|
+
if (workType === WORK_TYPE.PLAN) {
|
|
1938
1938
|
// Ensure plans directory exists before agent tries to write
|
|
1939
1939
|
if (!fs.existsSync(PLANS_DIR)) fs.mkdirSync(PLANS_DIR, { recursive: true });
|
|
1940
1940
|
const planFileName = `plan-${item.id.toLowerCase()}-${dateStamp()}.md`;
|
|
@@ -1949,7 +1949,7 @@ function discoverCentralWorkItems(config) {
|
|
|
1949
1949
|
}
|
|
1950
1950
|
|
|
1951
1951
|
// Inject plan-to-prd variables — read the plan file content for the playbook
|
|
1952
|
-
if (workType ===
|
|
1952
|
+
if (workType === WORK_TYPE.PLAN_TO_PRD && item.planFile) {
|
|
1953
1953
|
if (!fs.existsSync(PLANS_DIR)) fs.mkdirSync(PLANS_DIR, { recursive: true });
|
|
1954
1954
|
if (!fs.existsSync(PRD_DIR)) fs.mkdirSync(PRD_DIR, { recursive: true });
|
|
1955
1955
|
const planPath = path.join(PLANS_DIR, item.planFile);
|
|
@@ -1968,7 +1968,7 @@ function discoverCentralWorkItems(config) {
|
|
|
1968
1968
|
}
|
|
1969
1969
|
|
|
1970
1970
|
// Inject ask-specific variables for the ask playbook
|
|
1971
|
-
if (workType ===
|
|
1971
|
+
if (workType === WORK_TYPE.ASK) {
|
|
1972
1972
|
vars.question = item.title + (item.description ? '\n\n' + item.description : '');
|
|
1973
1973
|
vars.task_id = item.id;
|
|
1974
1974
|
vars.notes_content = '';
|
|
@@ -1986,7 +1986,7 @@ function discoverCentralWorkItems(config) {
|
|
|
1986
1986
|
const prompt = renderPlaybook(playbookName, vars) || renderPlaybook('work-item', vars);
|
|
1987
1987
|
if (!prompt) {
|
|
1988
1988
|
log('warn', `Dispatch: playbook '${playbookName}' failed to render for ${item.id}, resetting to pending`);
|
|
1989
|
-
item.status =
|
|
1989
|
+
item.status = WI_STATUS.PENDING;
|
|
1990
1990
|
continue;
|
|
1991
1991
|
}
|
|
1992
1992
|
|
|
@@ -2000,7 +2000,7 @@ function discoverCentralWorkItems(config) {
|
|
|
2000
2000
|
meta: { dispatchKey: key, source: 'central-work-item', item, planFileName: item.planFile || item._planFileName || null, branch: item.branch || item.featureBranch || `work/${item.id}` }
|
|
2001
2001
|
});
|
|
2002
2002
|
|
|
2003
|
-
item.status =
|
|
2003
|
+
item.status = WI_STATUS.DISPATCHED;
|
|
2004
2004
|
item.dispatched_at = ts();
|
|
2005
2005
|
item.dispatched_to = agentId;
|
|
2006
2006
|
setCooldown(key);
|
|
@@ -2032,9 +2032,9 @@ function discoverWork(config) {
|
|
|
2032
2032
|
|
|
2033
2033
|
// Source 1: Pull Requests → fixes, reviews, build-test
|
|
2034
2034
|
const prWork = discoverFromPrs(config, project);
|
|
2035
|
-
allFixes.push(...prWork.filter(w => w.type ===
|
|
2036
|
-
allReviews.push(...prWork.filter(w => w.type ===
|
|
2037
|
-
allWorkItems.push(...prWork.filter(w => w.type ===
|
|
2035
|
+
allFixes.push(...prWork.filter(w => w.type === WORK_TYPE.FIX));
|
|
2036
|
+
allReviews.push(...prWork.filter(w => w.type === WORK_TYPE.REVIEW));
|
|
2037
|
+
allWorkItems.push(...prWork.filter(w => w.type === WORK_TYPE.TEST));
|
|
2038
2038
|
|
|
2039
2039
|
// Side-effect: specs → work items (picked up below)
|
|
2040
2040
|
materializeSpecsAsWorkItems(config, project);
|
|
@@ -2059,14 +2059,14 @@ function discoverWork(config) {
|
|
|
2059
2059
|
const items = safeJson(centralPath) || [];
|
|
2060
2060
|
let added = 0;
|
|
2061
2061
|
for (const item of scheduledWork) {
|
|
2062
|
-
if (item.type ===
|
|
2062
|
+
if (item.type === WORK_TYPE.MEETING) {
|
|
2063
2063
|
// Create a real multi-agent meeting instead of a single-agent work item
|
|
2064
2064
|
const sched = (config.schedules || []).find(s => s.id === item._scheduleId);
|
|
2065
2065
|
const participants = (sched && sched.participants) || [];
|
|
2066
2066
|
const meeting = createMeeting({ title: item.title, agenda: item.description, participants });
|
|
2067
2067
|
log('info', `Scheduled meeting created: ${item._scheduleId} → ${meeting.id} (${participants.length} participants)`);
|
|
2068
2068
|
} else {
|
|
2069
|
-
if (!items.some(i => i._scheduleId === item._scheduleId && i.status !==
|
|
2069
|
+
if (!items.some(i => i._scheduleId === item._scheduleId && i.status !== WI_STATUS.DONE && i.status !== WI_STATUS.FAILED)) {
|
|
2070
2070
|
items.push(item);
|
|
2071
2071
|
added++;
|
|
2072
2072
|
log('info', `Scheduled task fired: ${item._scheduleId} → ${item.title}`);
|
|
@@ -2259,9 +2259,9 @@ async function tickInner() {
|
|
|
2259
2259
|
const wiPath = projectWorkItemsPath(project);
|
|
2260
2260
|
const items = safeJson(wiPath) || [];
|
|
2261
2261
|
let changed = false;
|
|
2262
|
-
const failedIds = new Set(items.filter(w => w.status ===
|
|
2262
|
+
const failedIds = new Set(items.filter(w => w.status === WI_STATUS.FAILED).map(w => w.id));
|
|
2263
2263
|
const pendingWithBlockedDeps = items.filter(w =>
|
|
2264
|
-
w.status ===
|
|
2264
|
+
w.status === WI_STATUS.PENDING && (w.depends_on || []).some(d => failedIds.has(d))
|
|
2265
2265
|
);
|
|
2266
2266
|
|
|
2267
2267
|
if (pendingWithBlockedDeps.length > 0) {
|
|
@@ -2269,11 +2269,11 @@ async function tickInner() {
|
|
|
2269
2269
|
for (const item of items) {
|
|
2270
2270
|
if (item.status !== 'failed') continue;
|
|
2271
2271
|
// Only retry if something depends on this item
|
|
2272
|
-
const isBlocking = items.some(w => w.status ===
|
|
2272
|
+
const isBlocking = items.some(w => w.status === WI_STATUS.PENDING && (w.depends_on || []).includes(item.id));
|
|
2273
2273
|
if (!isBlocking) continue;
|
|
2274
2274
|
|
|
2275
2275
|
log('info', `Stall recovery: auto-retrying ${item.id} (blocking ${pendingWithBlockedDeps.filter(w => (w.depends_on || []).includes(item.id)).length} items)`);
|
|
2276
|
-
item.status =
|
|
2276
|
+
item.status = WI_STATUS.PENDING;
|
|
2277
2277
|
item._retryCount = 0;
|
|
2278
2278
|
delete item.failReason;
|
|
2279
2279
|
delete item.failedAt;
|
|
@@ -2303,9 +2303,9 @@ async function tickInner() {
|
|
|
2303
2303
|
|
|
2304
2304
|
// Un-fail dependent items that were cascade-failed
|
|
2305
2305
|
if (changed) {
|
|
2306
|
-
const retriedIds = new Set(items.filter(w => w.status ===
|
|
2306
|
+
const retriedIds = new Set(items.filter(w => w.status === WI_STATUS.PENDING && w._retryCount === 0).map(w => w.id));
|
|
2307
2307
|
for (const dep of items) {
|
|
2308
|
-
if (dep.status ===
|
|
2308
|
+
if (dep.status === WI_STATUS.FAILED && dep.failReason === 'Dependency failed — cannot proceed') {
|
|
2309
2309
|
const blockers = (dep.depends_on || []).filter(d => retriedIds.has(d));
|
|
2310
2310
|
if (blockers.length > 0) {
|
|
2311
2311
|
log('info', `Stall recovery: un-failing ${dep.id} (blocker ${blockers.join(',')} retried)`);
|
|
@@ -2418,7 +2418,7 @@ async function tickInner() {
|
|
|
2418
2418
|
if (wiPath) {
|
|
2419
2419
|
const items = safeJson(wiPath) || [];
|
|
2420
2420
|
const wi = items.find(i => i.id === item.meta.item.id);
|
|
2421
|
-
if (wi && wi.status ===
|
|
2421
|
+
if (wi && wi.status === WI_STATUS.DISPATCHED) {
|
|
2422
2422
|
// completeDispatch didn't update the work item — re-queue manually
|
|
2423
2423
|
wi.status = 'pending';
|
|
2424
2424
|
wi._retryCount = (wi._retryCount || 0) + 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.319",
|
|
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"
|