@yemi33/minions 0.1.418 → 0.1.420
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 +6 -1
- package/dashboard.js +2 -3
- package/engine/ado.js +1 -1
- package/engine/dispatch.js +1 -1
- package/engine/lifecycle.js +1 -0
- package/engine/llm.js +2 -15
- package/engine/pipeline.js +30 -13
- package/engine/preflight.js +7 -1
- package/engine/shared.js +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.420 (2026-04-06)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- Low-priority cleanup: dedupe regex, consolidate streaming parse, add path validation alignment
|
|
7
|
+
- Fix 6 medium bugs: dispatch pruning, null guards, skill regex, meeting advancement, CLI PID check, pipeline retry
|
|
6
8
|
- Convert remaining lifecycle.js safeWrite calls to mutateJsonFileLocked
|
|
7
9
|
|
|
10
|
+
### Fixes
|
|
11
|
+
- address review feedback — pipeline.js socket leak and magic numbers
|
|
12
|
+
|
|
8
13
|
## 0.1.417 (2026-04-06)
|
|
9
14
|
|
|
10
15
|
### Features
|
package/dashboard.js
CHANGED
|
@@ -2329,9 +2329,8 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2329
2329
|
try {
|
|
2330
2330
|
const body = await readBody(req);
|
|
2331
2331
|
if (!body.file) return jsonReply(res, 400, { error: 'file required' });
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
}
|
|
2332
|
+
try { shared.sanitizePath(body.file, body.file.endsWith('.json') ? PRD_DIR : PLANS_DIR); }
|
|
2333
|
+
catch { return jsonReply(res, 400, { error: 'invalid filename' }); }
|
|
2335
2334
|
const isJson = body.file.endsWith('.json');
|
|
2336
2335
|
const targetDir = isJson ? PRD_DIR : PLANS_DIR;
|
|
2337
2336
|
const archivePath = path.join(targetDir, 'archive', body.file);
|
package/engine/ado.js
CHANGED
|
@@ -381,7 +381,7 @@ async function reconcilePrs(config) {
|
|
|
381
381
|
const title = adoPr.title || '';
|
|
382
382
|
// Extract item ID from branch name or PR title (e.g., feat(P-2cafdc2a): ...)
|
|
383
383
|
const branchMatch = branch.match(/(P-[a-z0-9]{6,})/i) || branch.match(/(W-[a-z0-9]{6,})/i) || branch.match(/(PL-[a-z0-9]{6,})/i);
|
|
384
|
-
const titleMatch = title.match(/\((P-[a-z0-9]{6,})\)/) || title.match(/\((W-[a-z0-9]{6,})\)/) || title.match(/\((
|
|
384
|
+
const titleMatch = title.match(/\((P-[a-z0-9]{6,})\)/) || title.match(/\((W-[a-z0-9]{6,})\)/) || title.match(/\((PL-[a-z0-9]{6,})\)/);
|
|
385
385
|
const linkedItemId = branchMatch?.[1] || titleMatch?.[1] || null;
|
|
386
386
|
const linkedItem = linkedItemId ? allItems.find(i => i.id === linkedItemId) : null;
|
|
387
387
|
const confirmedItemId = linkedItem ? linkedItemId : null;
|
package/engine/dispatch.js
CHANGED
|
@@ -87,7 +87,7 @@ function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', res
|
|
|
87
87
|
if (reason) item.reason = reason;
|
|
88
88
|
if (resultSummary) item.resultSummary = resultSummary;
|
|
89
89
|
delete item.prompt;
|
|
90
|
-
if (dispatch.completed.length
|
|
90
|
+
if (dispatch.completed.length >= 100) {
|
|
91
91
|
dispatch.completed = dispatch.completed.slice(-100);
|
|
92
92
|
}
|
|
93
93
|
dispatch.completed.push(item);
|
package/engine/lifecycle.js
CHANGED
|
@@ -901,6 +901,7 @@ function extractSkillsFromOutput(output, agentId, dispatchItem, config) {
|
|
|
901
901
|
if (!fullText) fullText = output;
|
|
902
902
|
const skillBlocks = [];
|
|
903
903
|
const skillRegex = /```skill\s*\n([\s\S]*?)```/g;
|
|
904
|
+
skillRegex.lastIndex = 0;
|
|
904
905
|
let match;
|
|
905
906
|
while ((match = skillRegex.exec(fullText)) !== null) {
|
|
906
907
|
skillBlocks.push(match[1].trim());
|
package/engine/llm.js
CHANGED
|
@@ -160,25 +160,12 @@ function callLLMStreaming(promptText, sysPromptText, { timeout = 120000, label =
|
|
|
160
160
|
if (block.type === 'text' && block.text && block.text !== lastTextSent) {
|
|
161
161
|
lastTextSent = block.text;
|
|
162
162
|
onChunk(block.text);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
} catch { /* incomplete JSON or non-JSON line */ }
|
|
167
|
-
}
|
|
168
|
-
// Also emit tool_use events so the frontend can show "Using tool: Read..."
|
|
169
|
-
for (const line of lines) {
|
|
170
|
-
const trimmed = line.trim();
|
|
171
|
-
if (!trimmed || !trimmed.startsWith('{')) continue;
|
|
172
|
-
try {
|
|
173
|
-
const obj = JSON.parse(trimmed);
|
|
174
|
-
if (obj.type === 'assistant' && obj.message?.content) {
|
|
175
|
-
for (const block of obj.message.content) {
|
|
176
|
-
if (block.type === 'tool_use' && block.name && onToolUse) {
|
|
163
|
+
} else if (block.type === 'tool_use' && block.name && onToolUse) {
|
|
177
164
|
onToolUse(block.name, block.input);
|
|
178
165
|
}
|
|
179
166
|
}
|
|
180
167
|
}
|
|
181
|
-
} catch {}
|
|
168
|
+
} catch { /* incomplete JSON or non-JSON line */ }
|
|
182
169
|
}
|
|
183
170
|
});
|
|
184
171
|
proc.stderr.on('data', d => { stderr += d.toString(); });
|
package/engine/pipeline.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const shared = require('./shared');
|
|
10
|
-
const { safeJson, safeWrite, safeRead, safeReadDir, uid, log, ts, dateStamp, mutateJsonFileLocked, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, PIPELINE_STATUS, STAGE_TYPE, MEETING_STATUS } = shared;
|
|
10
|
+
const { safeJson, safeWrite, safeRead, safeReadDir, uid, log, ts, dateStamp, mutateJsonFileLocked, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, PIPELINE_STATUS, STAGE_TYPE, MEETING_STATUS, ENGINE_DEFAULTS } = shared;
|
|
11
|
+
const http = require('http');
|
|
11
12
|
const { parseCronExpr, shouldRunNow } = require('./scheduler');
|
|
12
13
|
|
|
13
14
|
const PIPELINES_DIR = path.join(__dirname, '..', 'pipelines');
|
|
@@ -313,18 +314,34 @@ function executeApiStage(stage, stageState, run) {
|
|
|
313
314
|
for (const call of calls) {
|
|
314
315
|
const url = `http://localhost:${process.env.MINIONS_PORT || 7331}${call.endpoint}`;
|
|
315
316
|
const body = typeof call.body === 'string' ? call.body : JSON.stringify(call.body || {});
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
317
|
+
const maxAttempts = ENGINE_DEFAULTS.pipelineApiRetries;
|
|
318
|
+
const retryDelay = ENGINE_DEFAULTS.pipelineApiRetryDelay;
|
|
319
|
+
const makeRequest = (attempt) => {
|
|
320
|
+
try {
|
|
321
|
+
const parsed = new URL(url);
|
|
322
|
+
const req = http.request({
|
|
323
|
+
hostname: parsed.hostname, port: parsed.port, path: parsed.pathname,
|
|
324
|
+
method: call.method || 'POST',
|
|
325
|
+
headers: { 'Content-Type': 'application/json' },
|
|
326
|
+
}, (res) => {
|
|
327
|
+
res.resume(); // drain body to free socket
|
|
328
|
+
if (res.statusCode >= 400) {
|
|
329
|
+
log('warn', `Pipeline API call to ${call.endpoint} returned ${res.statusCode} (attempt ${attempt})`);
|
|
330
|
+
if (attempt < maxAttempts) setTimeout(() => makeRequest(attempt + 1), retryDelay);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
req.on('error', (err) => {
|
|
334
|
+
log('warn', `Pipeline API call to ${call.endpoint} failed: ${err.message} (attempt ${attempt})`);
|
|
335
|
+
if (attempt < maxAttempts) setTimeout(() => makeRequest(attempt + 1), retryDelay);
|
|
336
|
+
});
|
|
337
|
+
req.write(body);
|
|
338
|
+
req.end();
|
|
339
|
+
} catch (e) {
|
|
340
|
+
log('warn', `Pipeline API call to ${call.endpoint} threw: ${e.message} (attempt ${attempt})`);
|
|
341
|
+
if (attempt < maxAttempts) setTimeout(() => makeRequest(attempt + 1), retryDelay);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
makeRequest(1);
|
|
328
345
|
}
|
|
329
346
|
return { status: PIPELINE_STATUS.COMPLETED, completedAt: ts() };
|
|
330
347
|
}
|
package/engine/preflight.js
CHANGED
|
@@ -27,7 +27,13 @@ function findClaudeBinary() {
|
|
|
27
27
|
path.join(path.dirname(process.execPath), '..', 'lib', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
|
|
28
28
|
// fnm / volta — sibling to the node binary
|
|
29
29
|
path.join(path.dirname(process.execPath), 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
|
|
30
|
-
].filter(
|
|
30
|
+
].filter(p => {
|
|
31
|
+
if (!p) {
|
|
32
|
+
if (process.env.MINIONS_DEBUG) console.log('[preflight] Dropped empty CLI search path entry');
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
});
|
|
31
37
|
for (const p of searchPaths) {
|
|
32
38
|
try { if (fs.existsSync(p)) return p; } catch {}
|
|
33
39
|
}
|
package/engine/shared.js
CHANGED
|
@@ -438,6 +438,8 @@ const ENGINE_DEFAULTS = {
|
|
|
438
438
|
evalMaxIterations: 3, // max review→fix cycles before escalating to human
|
|
439
439
|
evalMaxCost: null, // USD ceiling per work item across all eval iterations; null = no limit (gather baseline data first)
|
|
440
440
|
maxRetries: 3, // max dispatch retries before marking work item as failed
|
|
441
|
+
pipelineApiRetries: 2, // max attempts for pipeline API calls
|
|
442
|
+
pipelineApiRetryDelay: 2000, // ms delay between pipeline API retries
|
|
441
443
|
};
|
|
442
444
|
|
|
443
445
|
// ─── Status & Type Constants ─────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.420",
|
|
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"
|