@yemi33/minions 0.1.129 → 0.1.130
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 +10 -0
- package/dashboard.js +8 -7
- package/engine/lifecycle.js +11 -7
- package/engine/shared.js +8 -5
- package/engine/timeout.js +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/dashboard.js
CHANGED
|
@@ -1662,7 +1662,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
1662
1662
|
const body = await readBody(req);
|
|
1663
1663
|
if (!body.file) return jsonReply(res, 400, { error: 'file required' });
|
|
1664
1664
|
const file = body.file;
|
|
1665
|
-
if (file.includes('..') || file.includes('\0')) return jsonReply(res, 400, { error: 'invalid' });
|
|
1665
|
+
if (file.includes('..') || file.includes('\0') || file.includes('/') || file.includes('\\')) return jsonReply(res, 400, { error: 'invalid' });
|
|
1666
1666
|
|
|
1667
1667
|
const isJson = file.endsWith('.json');
|
|
1668
1668
|
const sourceDir = isJson ? PRD_DIR : PLANS_DIR;
|
|
@@ -1701,7 +1701,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
1701
1701
|
const body = await readBody(req);
|
|
1702
1702
|
if (!body.file) return jsonReply(res, 400, { error: 'file required' });
|
|
1703
1703
|
const file = body.file;
|
|
1704
|
-
if (file.includes('..') || file.includes('\0')) return jsonReply(res, 400, { error: 'invalid' });
|
|
1704
|
+
if (file.includes('..') || file.includes('\0') || file.includes('/') || file.includes('\\')) return jsonReply(res, 400, { error: 'invalid' });
|
|
1705
1705
|
|
|
1706
1706
|
const isJson = file.endsWith('.json');
|
|
1707
1707
|
const targetDir = isJson ? PRD_DIR : PLANS_DIR;
|
|
@@ -2660,7 +2660,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
2660
2660
|
const body = await readBody(req);
|
|
2661
2661
|
const { name, category } = body;
|
|
2662
2662
|
if (!name) return jsonReply(res, 400, { error: 'name required' });
|
|
2663
|
-
if (name.includes('..') || name.includes('\0')) return jsonReply(res, 400, { error: 'Invalid file name' });
|
|
2663
|
+
if (name.includes('..') || name.includes('\0') || name.includes('/') || name.includes('\\')) return jsonReply(res, 400, { error: 'Invalid file name' });
|
|
2664
2664
|
if (!category || !shared.KB_CATEGORIES.includes(category)) {
|
|
2665
2665
|
return jsonReply(res, 400, { error: 'category required: ' + shared.KB_CATEGORIES.join(', ') });
|
|
2666
2666
|
}
|
|
@@ -2737,13 +2737,14 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
2737
2737
|
const params = new URL(req.url, 'http://localhost').searchParams;
|
|
2738
2738
|
const file = params.get('file');
|
|
2739
2739
|
const dir = params.get('dir');
|
|
2740
|
-
if (!file || file.includes('..') || file.includes('\0')) { res.statusCode = 400; res.end('Invalid file'); return; }
|
|
2740
|
+
if (!file || file.includes('..') || file.includes('\0') || file.includes('/') || file.includes('\\')) { res.statusCode = 400; res.end('Invalid file'); return; }
|
|
2741
2741
|
|
|
2742
2742
|
let content = '';
|
|
2743
2743
|
if (dir) {
|
|
2744
|
-
// Direct path from collectSkillFiles
|
|
2745
|
-
const
|
|
2746
|
-
|
|
2744
|
+
// Direct path from collectSkillFiles — validate resolved path stays within expected dir
|
|
2745
|
+
const resolvedDir = path.resolve(dir.replace(/\//g, path.sep));
|
|
2746
|
+
const fullPath = path.join(resolvedDir, file);
|
|
2747
|
+
if (fullPath.startsWith(resolvedDir)) content = safeRead(fullPath) || '';
|
|
2747
2748
|
}
|
|
2748
2749
|
if (!content) {
|
|
2749
2750
|
// Fallback: search Claude Code skills, then project skills
|
package/engine/lifecycle.js
CHANGED
|
@@ -500,14 +500,18 @@ function syncPrdItemStatus(itemId, status, sourcePlan) {
|
|
|
500
500
|
const files = sourcePlan ? [sourcePlan] : require('fs').readdirSync(prdDir).filter(f => f.endsWith('.json'));
|
|
501
501
|
for (const pf of files) {
|
|
502
502
|
const fpath = path.join(prdDir, pf);
|
|
503
|
+
mutateJsonFileLocked(fpath, (plan) => {
|
|
504
|
+
if (!plan?.missing_features) return plan;
|
|
505
|
+
const feature = plan.missing_features.find(f => f.id === itemId);
|
|
506
|
+
if (feature && feature.status !== status) {
|
|
507
|
+
feature.status = status;
|
|
508
|
+
}
|
|
509
|
+
return plan;
|
|
510
|
+
});
|
|
511
|
+
// Check if we found it (read back to verify)
|
|
503
512
|
const plan = safeJson(fpath);
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (feature && feature.status !== status) {
|
|
507
|
-
feature.status = status;
|
|
508
|
-
shared.safeWrite(fpath, plan);
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
513
|
+
const feature = plan?.missing_features?.find(f => f.id === itemId);
|
|
514
|
+
if (feature && feature.status === status) return;
|
|
511
515
|
}
|
|
512
516
|
} catch (err) { log('warn', `PRD status sync: ${err.message}`); }
|
|
513
517
|
}
|
package/engine/shared.js
CHANGED
|
@@ -22,11 +22,14 @@ function log(level, msg, meta = {}) {
|
|
|
22
22
|
const entry = { timestamp: ts(), level, message: msg, ...meta };
|
|
23
23
|
console.log(`[${logTs()}] [${level}] ${msg}`);
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
try {
|
|
26
|
+
mutateJsonFileLocked(LOG_PATH, (logData) => {
|
|
27
|
+
if (!Array.isArray(logData)) logData = logData?.entries || [];
|
|
28
|
+
logData.push(entry);
|
|
29
|
+
if (logData.length >= 2500) logData.splice(0, logData.length - 2000);
|
|
30
|
+
return logData;
|
|
31
|
+
}, { defaultValue: [] });
|
|
32
|
+
} catch { /* logging should never crash the caller */ }
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
// ── File I/O ─────────────────────────────────────────────────────────────────
|
package/engine/timeout.js
CHANGED
|
@@ -248,6 +248,8 @@ function checkTimeouts(config) {
|
|
|
248
248
|
const isActive = possibleKeys.some(k => activeKeys.has(k)) ||
|
|
249
249
|
(dispatchData.active || []).some(d => d.meta?.item?.id === item.id);
|
|
250
250
|
if (!isActive) {
|
|
251
|
+
// Don't revive items that were explicitly failed for non-retryable reasons
|
|
252
|
+
if (item.status === 'failed' && item.failReason && !item.failReason.includes('Agent died')) continue;
|
|
251
253
|
const retries = (item._retryCount || 0);
|
|
252
254
|
if (retries < 3) {
|
|
253
255
|
log('info', `Reconcile: work item ${item.id} agent died — auto-retry ${retries + 1}/3`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.130",
|
|
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"
|