@yemi33/minions 0.1.803 → 0.1.805
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/render-prd.js +16 -10
- package/dashboard.js +68 -1
- package/engine/ado.js +4 -2
- package/engine/timeout.js +2 -2
- package/engine.js +16 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.805 (2026-04-10)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
6
|
- configurable ignored comment authors — auto-filtered, never trigger fixes
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
- cap review→fix cycles at evalMaxIterations (default 3)
|
|
10
10
|
|
|
11
11
|
### Fixes
|
|
12
|
+
- prevent false [process-exit] detection from tool result content
|
|
13
|
+
- allow PRD item retry when work item is missing (closes #781) (#807)
|
|
12
14
|
- handle force-pushed dep branches on retry (closes #738) (#806)
|
|
13
15
|
- project-scoped skill work items instruct wrong file path format (closes #790) (#804)
|
|
14
16
|
- move ignoredAuthors construction outside inner comment loop (ADO)
|
|
@@ -171,9 +171,10 @@ function renderPrdProgress(prog) {
|
|
|
171
171
|
const agentLabel = wiAgent ? '<span style="font-size:9px;color:var(--muted)" title="' + escHtml(wiAgent.name || wi.dispatched_to) + '">' +
|
|
172
172
|
(wiAgent.emoji || '') + ' ' + escHtml(wiAgent.name || wi.dispatched_to) + '</span>' : '';
|
|
173
173
|
|
|
174
|
-
// Requeue button for failed items
|
|
175
|
-
const canRequeue = wi && (wi.status === 'failed' || i.status === 'failed')
|
|
176
|
-
|
|
174
|
+
// Requeue button for failed items or PRD items with no work item (orphaned/deleted)
|
|
175
|
+
const canRequeue = (wi && (wi.status === 'failed' || i.status === 'failed')) ||
|
|
176
|
+
(!wi && i.status && i.status !== 'missing' && i.status !== 'done' && i.status !== 'planned');
|
|
177
|
+
const requeueState = getPrdRequeueState(wi ? wi.id : i.id);
|
|
177
178
|
let requeueBtn = '';
|
|
178
179
|
if (requeueState && requeueState.status === 'pending') {
|
|
179
180
|
requeueBtn = '<span style="color:var(--yellow);cursor:wait;font-size:9px;padding:1px 5px;background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.35);border-radius:3px" title="Retry request in progress">requeuing…</span>';
|
|
@@ -182,7 +183,7 @@ function renderPrdProgress(prog) {
|
|
|
182
183
|
} else if (requeueState && requeueState.status === 'error') {
|
|
183
184
|
requeueBtn = '<span style="color:var(--red);cursor:default;font-size:9px;padding:1px 5px;background:rgba(248,81,73,0.1);border:1px solid rgba(248,81,73,0.35);border-radius:3px" title="' + escHtml(requeueState.message || 'Retry failed') + '">retry failed</span>';
|
|
184
185
|
} else if (canRequeue) {
|
|
185
|
-
requeueBtn = '<span onclick="event.stopPropagation();prdItemRequeue(\'' + escHtml(wi.id) + '\',\'' + escHtml(wi._source || '') + '\')" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 5px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px" title="Requeue this work item">retry</span>';
|
|
186
|
+
requeueBtn = '<span onclick="event.stopPropagation();prdItemRequeue(\'' + escHtml(wi ? wi.id : i.id) + '\',\'' + escHtml(wi ? (wi._source || '') : '') + '\',\'' + escHtml(i.source || '') + '\')" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 5px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px" title="Requeue this work item">retry</span>';
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
return '<div class="prd-item-row st-' + (i.status || 'missing') + '" style="flex-wrap:wrap;cursor:pointer" onclick="prdItemEdit(\'' + src + '\',\'' + iid + '\')">' +
|
|
@@ -381,8 +382,8 @@ function renderPrdProgress(prog) {
|
|
|
381
382
|
(agentDisplay ? '<span style="font-size:8px;color:var(--muted)">' + agentDisplay + '</span>' : '') +
|
|
382
383
|
(function() {
|
|
383
384
|
const w = wi[i.id];
|
|
384
|
-
|
|
385
|
-
const rq = getPrdRequeueState(
|
|
385
|
+
const rqId = w ? w.id : i.id;
|
|
386
|
+
const rq = getPrdRequeueState(rqId);
|
|
386
387
|
if (rq && rq.status === 'pending') {
|
|
387
388
|
return '<span style="font-size:8px;color:var(--yellow);cursor:wait;padding:1px 4px;background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.35);border-radius:3px">requeuing…</span>';
|
|
388
389
|
}
|
|
@@ -392,8 +393,11 @@ function renderPrdProgress(prog) {
|
|
|
392
393
|
if (rq && rq.status === 'error') {
|
|
393
394
|
return '<span style="font-size:8px;color:var(--red);cursor:default;padding:1px 4px;background:rgba(248,81,73,0.1);border:1px solid rgba(248,81,73,0.35);border-radius:3px" title="' + escHtml(rq.message || 'Retry failed') + '">failed</span>';
|
|
394
395
|
}
|
|
395
|
-
|
|
396
|
-
|
|
396
|
+
// Show retry for failed items, or PRD items with no work item (orphaned/deleted)
|
|
397
|
+
const canRetry = (w && i.status === 'failed') ||
|
|
398
|
+
(!w && i.status && i.status !== 'missing' && i.status !== 'done' && i.status !== 'planned');
|
|
399
|
+
if (!canRetry) return '';
|
|
400
|
+
return '<span onclick="event.stopPropagation();prdItemRequeue(\'' + escHtml(rqId) + '\',\'' + escHtml(w ? (w._source || '') : '') + '\',\'' + escHtml(i.source || '') + '\')" style="font-size:8px;color:var(--green);cursor:pointer;padding:1px 4px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">retry</span>';
|
|
397
401
|
})() +
|
|
398
402
|
(deps ? '<span style="font-size:8px;color:var(--muted)" title="Depends on: ' + escHtml(deps) + '">deps: ' + escHtml(deps) + '</span>' : '') +
|
|
399
403
|
'</div>' +
|
|
@@ -713,13 +717,15 @@ async function prdItemRemove(source, itemId) {
|
|
|
713
717
|
} catch (e) { alert('Error: ' + e.message); refresh(); }
|
|
714
718
|
}
|
|
715
719
|
|
|
716
|
-
async function prdItemRequeue(workItemId, source) {
|
|
720
|
+
async function prdItemRequeue(workItemId, source, prdFile) {
|
|
717
721
|
setPrdRequeueState(workItemId, { status: 'pending', message: '' });
|
|
718
722
|
rerenderPrdFromCache();
|
|
719
723
|
try {
|
|
724
|
+
const payload = { id: workItemId, source };
|
|
725
|
+
if (prdFile) payload.prdFile = prdFile;
|
|
720
726
|
const res = await fetch('/api/work-items/retry', {
|
|
721
727
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
722
|
-
body: JSON.stringify(
|
|
728
|
+
body: JSON.stringify(payload)
|
|
723
729
|
});
|
|
724
730
|
if (res.ok) {
|
|
725
731
|
setPrdRequeueState(workItemId, { status: 'queued', until: Date.now() + 10000 });
|
package/dashboard.js
CHANGED
|
@@ -1040,7 +1040,74 @@ const server = http.createServer(async (req, res) => {
|
|
|
1040
1040
|
}
|
|
1041
1041
|
}
|
|
1042
1042
|
}
|
|
1043
|
-
|
|
1043
|
+
// If no work item found, attempt to re-materialize from PRD item definition
|
|
1044
|
+
if (!wiPath) {
|
|
1045
|
+
const prdFile = body.prdFile;
|
|
1046
|
+
if (!prdFile) return jsonReply(res, 404, { error: 'work item not found in any source' });
|
|
1047
|
+
|
|
1048
|
+
// Look up PRD item to create a new work item on-demand
|
|
1049
|
+
const prdPath = path.join(PRD_DIR, prdFile);
|
|
1050
|
+
const plan = shared.safeJson(prdPath);
|
|
1051
|
+
if (!plan?.missing_features) return jsonReply(res, 404, { error: 'PRD file not found or invalid' });
|
|
1052
|
+
const prdItem = plan.missing_features.find(f => f.id === id);
|
|
1053
|
+
if (!prdItem) return jsonReply(res, 404, { error: 'PRD item not found in ' + prdFile });
|
|
1054
|
+
|
|
1055
|
+
// Determine target work-items file (project from PRD item or plan, fallback to central)
|
|
1056
|
+
const projName = prdItem.project || plan.project || prdFile.replace(/-\d{4}-\d{2}-\d{2}\.json$/, '');
|
|
1057
|
+
const proj = PROJECTS.find(p => p.name?.toLowerCase() === projName.toLowerCase());
|
|
1058
|
+
const targetWiPath = proj ? shared.projectWorkItemsPath(proj) : path.join(MINIONS_DIR, 'work-items.json');
|
|
1059
|
+
|
|
1060
|
+
// Create new work item from PRD item definition (same logic as materializePlansAsWorkItems)
|
|
1061
|
+
const complexity = prdItem.estimated_complexity || 'medium';
|
|
1062
|
+
const criteria = (prdItem.acceptance_criteria || []).map(c => `- ${c}`).join('\n');
|
|
1063
|
+
const newItem = {
|
|
1064
|
+
id,
|
|
1065
|
+
title: `Implement: ${prdItem.name}`,
|
|
1066
|
+
type: complexity === 'large' ? 'implement:large' : 'implement',
|
|
1067
|
+
priority: prdItem.priority || 'medium',
|
|
1068
|
+
description: `${prdItem.description || ''}\n\n**Plan:** ${prdFile}\n**Plan Item:** ${prdItem.id}\n**Complexity:** ${complexity}${criteria ? '\n\n**Acceptance Criteria:**\n' + criteria : ''}`,
|
|
1069
|
+
status: WI_STATUS.PENDING,
|
|
1070
|
+
created: new Date().toISOString(),
|
|
1071
|
+
createdBy: 'dashboard:prd-retry',
|
|
1072
|
+
sourcePlan: prdFile,
|
|
1073
|
+
depends_on: prdItem.depends_on || [],
|
|
1074
|
+
branchStrategy: plan.branch_strategy || 'parallel',
|
|
1075
|
+
featureBranch: plan.feature_branch || null,
|
|
1076
|
+
project: prdItem.project || plan.project || null,
|
|
1077
|
+
_source: proj?.name || 'central',
|
|
1078
|
+
_retryCount: 0,
|
|
1079
|
+
};
|
|
1080
|
+
mutateWorkItems(targetWiPath, items => { items.push(newItem); });
|
|
1081
|
+
|
|
1082
|
+
// Reset PRD item status to pending
|
|
1083
|
+
try {
|
|
1084
|
+
const lifecycle = require('./engine/lifecycle');
|
|
1085
|
+
lifecycle.syncPrdItemStatus(id, WI_STATUS.PENDING, prdFile);
|
|
1086
|
+
} catch (e) { console.error('PRD status sync:', e.message); }
|
|
1087
|
+
|
|
1088
|
+
// Clear dispatch history and cooldowns for this item
|
|
1089
|
+
const dispatchPath = path.join(MINIONS_DIR, 'engine', 'dispatch.json');
|
|
1090
|
+
const sourcePrefix = proj ? `work-${proj.name}-` : 'central-work-';
|
|
1091
|
+
const dispatchKey = sourcePrefix + id;
|
|
1092
|
+
try {
|
|
1093
|
+
mutateJsonFileLocked(dispatchPath, (dispatch) => {
|
|
1094
|
+
dispatch.completed = Array.isArray(dispatch.completed) ? dispatch.completed : [];
|
|
1095
|
+
dispatch.completed = dispatch.completed.filter(d => d.meta?.dispatchKey !== dispatchKey);
|
|
1096
|
+
dispatch.completed = dispatch.completed.filter(d => !d.meta?.parentKey || d.meta.parentKey !== dispatchKey);
|
|
1097
|
+
return dispatch;
|
|
1098
|
+
}, { defaultValue: { pending: [], active: [], completed: [] } });
|
|
1099
|
+
} catch (e) { console.error('dispatch cleanup:', e.message); }
|
|
1100
|
+
try {
|
|
1101
|
+
const cooldownPath = path.join(MINIONS_DIR, 'engine', 'cooldowns.json');
|
|
1102
|
+
const cooldowns = safeJsonObj(cooldownPath);
|
|
1103
|
+
if (cooldowns[dispatchKey]) {
|
|
1104
|
+
delete cooldowns[dispatchKey];
|
|
1105
|
+
safeWrite(cooldownPath, cooldowns);
|
|
1106
|
+
}
|
|
1107
|
+
} catch (e) { console.error('cooldown cleanup:', e.message); }
|
|
1108
|
+
|
|
1109
|
+
return jsonReply(res, 200, { ok: true, id, rematerialized: true });
|
|
1110
|
+
}
|
|
1044
1111
|
|
|
1045
1112
|
let found = false;
|
|
1046
1113
|
mutateJsonFileLocked(wiPath, (items) => {
|
package/engine/ado.js
CHANGED
|
@@ -63,7 +63,8 @@ async function getAdoToken() {
|
|
|
63
63
|
async function adoFetch(url, token, _retryCount = 0) {
|
|
64
64
|
const MAX_RETRIES = 1;
|
|
65
65
|
const res = await fetch(url, {
|
|
66
|
-
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
|
|
66
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
67
|
+
signal: AbortSignal.timeout(30000),
|
|
67
68
|
});
|
|
68
69
|
if (!res.ok) throw new Error(`ADO API ${res.status}: ${res.statusText}`);
|
|
69
70
|
const text = await res.text();
|
|
@@ -85,7 +86,8 @@ async function adoFetch(url, token, _retryCount = 0) {
|
|
|
85
86
|
/** Fetch raw text from ADO API (for build logs which aren't JSON). */
|
|
86
87
|
async function adoFetchText(url, token) {
|
|
87
88
|
const res = await fetch(url, {
|
|
88
|
-
headers: { 'Authorization': `Bearer ${token}` }
|
|
89
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
90
|
+
signal: AbortSignal.timeout(30000),
|
|
89
91
|
});
|
|
90
92
|
if (!res.ok) throw new Error(`ADO API ${res.status}: ${res.statusText}`);
|
|
91
93
|
return res.text();
|
package/engine/timeout.js
CHANGED
|
@@ -193,7 +193,7 @@ function checkTimeouts(config) {
|
|
|
193
193
|
fs.closeSync(fd);
|
|
194
194
|
liveLog = buf.toString('utf8');
|
|
195
195
|
} catch { /* ENOENT or read failure — liveLog stays undefined */ }
|
|
196
|
-
if (liveLog && (liveLog.includes('"type":"result"') || liveLog.includes('[process-exit]'))) {
|
|
196
|
+
if (liveLog && (liveLog.includes('"type":"result"') || liveLog.includes('\n[process-exit]'))) {
|
|
197
197
|
completedViaOutput = true;
|
|
198
198
|
const isSuccess = liveLog.includes('"subtype":"success"');
|
|
199
199
|
log('info', `Agent ${item.agent} (${item.id}) completed via output detection (${isSuccess ? 'success' : 'error'})`);
|
|
@@ -237,7 +237,7 @@ function checkTimeouts(config) {
|
|
|
237
237
|
if (liveLog) {
|
|
238
238
|
// If the output contains a result event or process-exit sentinel, the agent is done.
|
|
239
239
|
// Don't extend timeout for stale blocking tool calls from before the result (#716).
|
|
240
|
-
if (liveLog.includes('"type":"result"') || liveLog.includes('[process-exit]')) {
|
|
240
|
+
if (liveLog.includes('"type":"result"') || liveLog.includes('\n[process-exit]')) {
|
|
241
241
|
// Agent completed but close event didn't fire — let orphan/hung detection handle it.
|
|
242
242
|
// Don't set isBlocking — use base heartbeat timeout.
|
|
243
243
|
} else {
|
package/engine.js
CHANGED
|
@@ -1202,6 +1202,10 @@ function autoCleanPrdWorkItems(prdFile, config) {
|
|
|
1202
1202
|
}
|
|
1203
1203
|
return dispatch;
|
|
1204
1204
|
});
|
|
1205
|
+
// Reset PRD item status to 'missing' so engine re-materializes on next tick
|
|
1206
|
+
for (const id of deletedIds) {
|
|
1207
|
+
try { syncPrdItemStatus(id, 'missing', prdFile); } catch (e) { log('warn', `PRD status reset for ${id}: ${e.message}`); }
|
|
1208
|
+
}
|
|
1205
1209
|
log('info', `Plan sync: cleared ${deletedIds.length} pending/failed work items for ${prdFile}`);
|
|
1206
1210
|
}
|
|
1207
1211
|
}
|
|
@@ -2694,16 +2698,27 @@ let tickCount = 0;
|
|
|
2694
2698
|
const completedPlanCache = new Set();
|
|
2695
2699
|
|
|
2696
2700
|
let tickRunning = false;
|
|
2701
|
+
let _tickStartedAt = 0;
|
|
2702
|
+
const TICK_TIMEOUT_MS = 300000; // 5 min — force-release tick lock if stuck
|
|
2697
2703
|
|
|
2698
2704
|
async function tick() {
|
|
2699
|
-
if (tickRunning)
|
|
2705
|
+
if (tickRunning) {
|
|
2706
|
+
if (_tickStartedAt && Date.now() - _tickStartedAt > TICK_TIMEOUT_MS) {
|
|
2707
|
+
log('error', `Tick hung for ${Math.round((Date.now() - _tickStartedAt) / 1000)}s — force-releasing lock`);
|
|
2708
|
+
tickRunning = false;
|
|
2709
|
+
_tickStartedAt = 0;
|
|
2710
|
+
}
|
|
2711
|
+
return;
|
|
2712
|
+
}
|
|
2700
2713
|
tickRunning = true;
|
|
2714
|
+
_tickStartedAt = Date.now();
|
|
2701
2715
|
try {
|
|
2702
2716
|
await tickInner();
|
|
2703
2717
|
} catch (e) {
|
|
2704
2718
|
log('error', `Tick error: ${e.message}`);
|
|
2705
2719
|
} finally {
|
|
2706
2720
|
tickRunning = false;
|
|
2721
|
+
_tickStartedAt = 0;
|
|
2707
2722
|
}
|
|
2708
2723
|
}
|
|
2709
2724
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.805",
|
|
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"
|