@yemi33/minions 0.1.420 → 0.1.421
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 +8 -2
- package/dashboard.js +65 -58
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.421 (2026-04-06)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- Dashboard robustness — raw string fixes, plan steering clarity, safeWrite race fix
|
|
6
7
|
- Low-priority cleanup: dedupe regex, consolidate streaming parse, add path validation alignment
|
|
7
8
|
- Fix 6 medium bugs: dispatch pruning, null guards, skill regex, meeting advancement, CLI PID check, pipeline retry
|
|
8
9
|
- Convert remaining lifecycle.js safeWrite calls to mutateJsonFileLocked
|
|
9
10
|
|
|
10
11
|
### Fixes
|
|
12
|
+
- CC retry now drains queued messages after success
|
|
11
13
|
- address review feedback — pipeline.js socket leak and magic numbers
|
|
12
14
|
|
|
13
15
|
## 0.1.417 (2026-04-06)
|
|
@@ -344,8 +344,14 @@ function ccRetryLast() {
|
|
|
344
344
|
const el = document.getElementById('cc-messages');
|
|
345
345
|
if (el?.lastElementChild) el.lastElementChild.remove();
|
|
346
346
|
_ccMessages = _ccMessages.slice(0, -1); // remove error from history
|
|
347
|
-
// Resend
|
|
348
|
-
_ccDoSend(text.trim())
|
|
347
|
+
// Resend, then drain queue
|
|
348
|
+
_ccDoSend(text.trim()).then(async () => {
|
|
349
|
+
while (_ccQueue.length > 0) {
|
|
350
|
+
const next = _ccQueue.shift();
|
|
351
|
+
_renderQueueIndicator();
|
|
352
|
+
await _ccDoSend(next);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
349
355
|
}
|
|
350
356
|
|
|
351
357
|
async function _ccFetch(url, body) {
|
package/dashboard.js
CHANGED
|
@@ -792,8 +792,13 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
792
792
|
function readBody(req) {
|
|
793
793
|
return new Promise((resolve, reject) => {
|
|
794
794
|
let body = '';
|
|
795
|
-
|
|
796
|
-
|
|
795
|
+
const timeout = setTimeout(() => {
|
|
796
|
+
req.destroy();
|
|
797
|
+
reject(new Error('Request body timeout after 30s'));
|
|
798
|
+
}, 30000);
|
|
799
|
+
req.on('data', chunk => { body += chunk; if (body.length > 1e6) { clearTimeout(timeout); reject(new Error('Too large')); } });
|
|
800
|
+
req.on('end', () => { clearTimeout(timeout); try { resolve(JSON.parse(body)); } catch(e) { reject(e); } });
|
|
801
|
+
req.on('error', (e) => { clearTimeout(timeout); reject(e); });
|
|
797
802
|
});
|
|
798
803
|
}
|
|
799
804
|
|
|
@@ -1321,17 +1326,19 @@ const server = http.createServer(async (req, res) => {
|
|
|
1321
1326
|
let item;
|
|
1322
1327
|
mutateJsonFileLocked(planPath, (plan) => {
|
|
1323
1328
|
const target = (plan.missing_features || []).find(f => f.id === body.itemId);
|
|
1324
|
-
if (target)
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
}
|
|
1329
|
+
if (!target) return plan; // TOCTOU: item deleted between pre-check and lock acquisition
|
|
1330
|
+
if (body.name !== undefined) target.name = body.name;
|
|
1331
|
+
if (body.description !== undefined) target.description = body.description;
|
|
1332
|
+
if (body.priority !== undefined) target.priority = body.priority;
|
|
1333
|
+
if (body.estimated_complexity !== undefined) target.estimated_complexity = body.estimated_complexity;
|
|
1334
|
+
if (body.status !== undefined) target.status = body.status;
|
|
1335
|
+
item = target;
|
|
1332
1336
|
return plan;
|
|
1333
1337
|
}, { defaultValue: preCheck });
|
|
1334
1338
|
|
|
1339
|
+
// If item was deleted between pre-check and lock, return 404
|
|
1340
|
+
if (!item) return jsonReply(res, 404, { error: 'item not found in plan (deleted concurrently)' });
|
|
1341
|
+
|
|
1335
1342
|
// Feature 3: Sync edits to materialized work item if still pending
|
|
1336
1343
|
let workItemSynced = false;
|
|
1337
1344
|
const wiSyncPaths = [path.join(MINIONS_DIR, 'work-items.json')];
|
|
@@ -1929,8 +1936,8 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
1929
1936
|
mutateJsonFileLocked(wiPath, (items) => {
|
|
1930
1937
|
if (!Array.isArray(items)) return items;
|
|
1931
1938
|
for (const w of items) {
|
|
1932
|
-
if (w.sourcePlan === body.file && w.status ===
|
|
1933
|
-
w.status =
|
|
1939
|
+
if (w.sourcePlan === body.file && w.status === WI_STATUS.PAUSED && w._pausedBy === 'prd-pause') {
|
|
1940
|
+
w.status = WI_STATUS.PENDING;
|
|
1934
1941
|
delete w._pausedBy;
|
|
1935
1942
|
w._resumedAt = new Date().toISOString();
|
|
1936
1943
|
resumedItemIds.push(w.id);
|
|
@@ -1991,7 +1998,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
1991
1998
|
// Keep completed items as-is, reset everything else to pending.
|
|
1992
1999
|
if (w.completedAt || DONE_STATUSES.has(w.status)) continue;
|
|
1993
2000
|
|
|
1994
|
-
if (w.status ===
|
|
2001
|
+
if (w.status === WI_STATUS.DISPATCHED) {
|
|
1995
2002
|
// Kill the agent working on this item, if any.
|
|
1996
2003
|
const activeEntry = (dispatch.active || []).find(d => d.meta?.item?.id === w.id || d.meta?.dispatchKey?.includes(w.id));
|
|
1997
2004
|
if (activeEntry) {
|
|
@@ -2017,8 +2024,8 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2017
2024
|
}
|
|
2018
2025
|
}
|
|
2019
2026
|
|
|
2020
|
-
if (w.status !==
|
|
2021
|
-
w.status =
|
|
2027
|
+
if (w.status !== WI_STATUS.PAUSED) reset++;
|
|
2028
|
+
w.status = WI_STATUS.PAUSED;
|
|
2022
2029
|
w._pausedBy = 'prd-pause';
|
|
2023
2030
|
delete w._resumedAt;
|
|
2024
2031
|
delete w.dispatched_at;
|
|
@@ -2674,18 +2681,18 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
2674
2681
|
try {
|
|
2675
2682
|
const prdDir = path.join(MINIONS_DIR, 'prd');
|
|
2676
2683
|
if (fs.existsSync(prdDir)) {
|
|
2677
|
-
for (const
|
|
2678
|
-
if (!
|
|
2679
|
-
const prd = safeJson(path.join(prdDir,
|
|
2684
|
+
for (const prdFile of fs.readdirSync(prdDir)) {
|
|
2685
|
+
if (!prdFile.endsWith('.json')) continue;
|
|
2686
|
+
const prd = safeJson(path.join(prdDir, prdFile));
|
|
2680
2687
|
if (!prd || prd.source_plan !== planFile) continue;
|
|
2681
2688
|
if (prd.status === 'paused' || prd.status === 'rejected') continue;
|
|
2682
2689
|
// Found an active PRD linked to this plan — pause it
|
|
2683
2690
|
prd.status = 'paused';
|
|
2684
2691
|
prd.pausedAt = new Date().toISOString();
|
|
2685
2692
|
prd.pausedBy = 'plan-steering';
|
|
2686
|
-
safeWrite(path.join(prdDir,
|
|
2687
|
-
pausedPrd =
|
|
2688
|
-
// Pause work items (
|
|
2693
|
+
safeWrite(path.join(prdDir, prdFile), prd);
|
|
2694
|
+
pausedPrd = prdFile;
|
|
2695
|
+
// Pause work items linked to this PRD (sourcePlan = PRD filename)
|
|
2689
2696
|
const wiPaths = [path.join(MINIONS_DIR, 'work-items.json')];
|
|
2690
2697
|
for (const proj of PROJECTS) wiPaths.push(shared.projectWorkItemsPath(proj));
|
|
2691
2698
|
const dispatchPath = path.join(MINIONS_DIR, 'engine', 'dispatch.json');
|
|
@@ -2694,45 +2701,45 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
2694
2701
|
const resetItemIds = new Set();
|
|
2695
2702
|
for (const wiPath of wiPaths) {
|
|
2696
2703
|
try {
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
}
|
|
2717
|
-
}
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2704
|
+
mutateJsonFileLocked(wiPath, (items) => {
|
|
2705
|
+
if (!Array.isArray(items)) return items;
|
|
2706
|
+
for (const w of items) {
|
|
2707
|
+
if (w.sourcePlan !== prdFile) continue;
|
|
2708
|
+
if (w.completedAt || DONE_STATUSES.has(w.status)) continue;
|
|
2709
|
+
if (w.status === WI_STATUS.DISPATCHED) {
|
|
2710
|
+
const activeEntry = (dispatch.active || []).find(d => d.meta?.item?.id === w.id || d.meta?.dispatchKey?.includes(w.id));
|
|
2711
|
+
if (activeEntry) {
|
|
2712
|
+
const statusPath = path.join(MINIONS_DIR, 'agents', activeEntry.agent, 'status.json');
|
|
2713
|
+
try {
|
|
2714
|
+
const agentStatus = JSON.parse(safeRead(statusPath) || '{}');
|
|
2715
|
+
if (agentStatus.pid) {
|
|
2716
|
+
try {
|
|
2717
|
+
const safePid = shared.validatePid(agentStatus.pid);
|
|
2718
|
+
if (process.platform === 'win32') {
|
|
2719
|
+
require('child_process').execFileSync('taskkill', ['/PID', String(safePid), '/F', '/T'], { stdio: 'pipe', timeout: 5000, windowsHide: true });
|
|
2720
|
+
} else {
|
|
2721
|
+
process.kill(safePid, 'SIGTERM');
|
|
2722
|
+
}
|
|
2723
|
+
} catch { /* process may be dead or invalid PID */ }
|
|
2724
|
+
}
|
|
2725
|
+
agentStatus.status = 'idle';
|
|
2726
|
+
delete agentStatus.currentTask;
|
|
2727
|
+
delete agentStatus.dispatched;
|
|
2728
|
+
safeWrite(statusPath, agentStatus);
|
|
2729
|
+
} catch { /* agent reset */ }
|
|
2730
|
+
killedAgents.add(activeEntry.agent);
|
|
2731
|
+
}
|
|
2725
2732
|
}
|
|
2733
|
+
w.status = WI_STATUS.PAUSED;
|
|
2734
|
+
w._pausedBy = 'plan-steering';
|
|
2735
|
+
delete w.dispatched_at;
|
|
2736
|
+
delete w.dispatched_to;
|
|
2737
|
+
delete w.failReason;
|
|
2738
|
+
delete w.failedAt;
|
|
2739
|
+
if (w.id) resetItemIds.add(w.id);
|
|
2726
2740
|
}
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
delete w.dispatched_to;
|
|
2730
|
-
delete w.failReason;
|
|
2731
|
-
delete w.failedAt;
|
|
2732
|
-
changed = true;
|
|
2733
|
-
if (w.id) resetItemIds.add(w.id);
|
|
2734
|
-
}
|
|
2735
|
-
if (changed) safeWrite(wiPath, items);
|
|
2741
|
+
return items;
|
|
2742
|
+
}, { defaultValue: [] });
|
|
2736
2743
|
} catch { /* reset work items */ }
|
|
2737
2744
|
}
|
|
2738
2745
|
if (resetItemIds.size > 0 || killedAgents.size > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.421",
|
|
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"
|