@yemi33/minions 0.1.531 → 0.1.532
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 +5 -0
- package/dashboard/js/render-meetings.js +2 -1
- package/dashboard.js +176 -187
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -48,7 +48,7 @@ function renderMeetings(meetings) {
|
|
|
48
48
|
const dt = m.completedAt || m.createdAt;
|
|
49
49
|
const timeStr = dt ? new Date(dt).toLocaleString(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }) : '';
|
|
50
50
|
|
|
51
|
-
return '<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:8px;cursor:pointer" onclick="openMeetingDetail(\'' + escHtml(m.id) + '\')">' +
|
|
51
|
+
return '<div data-file="meetings/' + escHtml(m.id) + '.json" style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:8px;cursor:pointer;position:relative" onclick="openMeetingDetail(\'' + escHtml(m.id) + '\')">' +
|
|
52
52
|
'<div style="display:flex;justify-content:space-between;align-items:center">' +
|
|
53
53
|
'<strong style="font-size:13px">' + escHtml(m.title) + '</strong>' +
|
|
54
54
|
'<div style="display:flex;align-items:center;gap:8px">' +
|
|
@@ -78,6 +78,7 @@ function renderMeetings(meetings) {
|
|
|
78
78
|
el.innerHTML += '<div style="text-align:center;margin-top:8px"><button class="pr-pager-btn" style="font-size:10px" onclick="_toggleArchivedMeetings()">' +
|
|
79
79
|
(_showArchived ? 'Hide' : 'Show') + ' ' + archived.length + ' archived</button></div>';
|
|
80
80
|
}
|
|
81
|
+
restoreNotifBadges();
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
function _mtgPrev() { if (_mtgPage > 0) { _mtgPage--; refresh(); } }
|
package/dashboard.js
CHANGED
|
@@ -22,7 +22,7 @@ const shared = require('./engine/shared');
|
|
|
22
22
|
const queries = require('./engine/queries');
|
|
23
23
|
const os = require('os');
|
|
24
24
|
|
|
25
|
-
const { safeRead, safeReadDir, safeWrite, safeJson, safeJsonObj, safeJsonArr, safeUnlink, mutateJsonFileLocked, getProjects: _getProjects, DONE_STATUSES, WI_STATUS } = shared;
|
|
25
|
+
const { safeRead, safeReadDir, safeWrite, safeJson, safeJsonObj, safeJsonArr, safeUnlink, mutateJsonFileLocked, mutateWorkItems, getProjects: _getProjects, DONE_STATUSES, WI_STATUS } = shared;
|
|
26
26
|
const { getAgents, getAgentDetail, getPrdInfo, getWorkItems, getDispatchQueue,
|
|
27
27
|
getSkills, getInbox, getNotesWithMeta, getPullRequests,
|
|
28
28
|
getEngineLog, getMetrics, getKnowledgeBaseEntries, timeSince,
|
|
@@ -1381,9 +1381,6 @@ const server = http.createServer(async (req, res) => {
|
|
|
1381
1381
|
if (!body.title || !body.title.trim()) return jsonReply(res, 400, { error: 'title is required' });
|
|
1382
1382
|
// Write as a work item with type 'plan' — user must explicitly execute plan-to-prd after reviewing
|
|
1383
1383
|
const wiPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
1384
|
-
let items = [];
|
|
1385
|
-
const existing = safeRead(wiPath);
|
|
1386
|
-
if (existing) { try { items = JSON.parse(existing); } catch {} }
|
|
1387
1384
|
const id = 'W-' + shared.uid();
|
|
1388
1385
|
const item = {
|
|
1389
1386
|
id, title: body.title, type: 'plan',
|
|
@@ -1393,8 +1390,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1393
1390
|
};
|
|
1394
1391
|
if (body.project) item.project = body.project;
|
|
1395
1392
|
if (body.agent) item.agent = body.agent;
|
|
1396
|
-
items.push(item);
|
|
1397
|
-
safeWrite(wiPath, items);
|
|
1393
|
+
mutateWorkItems(wiPath, items => { items.push(item); });
|
|
1398
1394
|
return jsonReply(res, 200, { ok: true, id, agent: body.agent || '' });
|
|
1399
1395
|
} catch (e) { return jsonReply(res, 400, { error: e.message }); }
|
|
1400
1396
|
}
|
|
@@ -1465,18 +1461,18 @@ const server = http.createServer(async (req, res) => {
|
|
|
1465
1461
|
}
|
|
1466
1462
|
for (const wiPath of wiSyncPaths) {
|
|
1467
1463
|
try {
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1464
|
+
mutateWorkItems(wiPath, items => {
|
|
1465
|
+
const wi = items.find(w => w.sourcePlan === body.source && w.id === body.itemId);
|
|
1466
|
+
if (wi && wi.status === 'pending') {
|
|
1467
|
+
if (body.name !== undefined) wi.title = 'Implement: ' + body.name;
|
|
1468
|
+
if (body.description !== undefined) wi.description = body.description;
|
|
1469
|
+
if (body.priority !== undefined) wi.priority = body.priority;
|
|
1470
|
+
if (body.estimated_complexity !== undefined) {
|
|
1471
|
+
wi.type = body.estimated_complexity === 'large' ? 'implement:large' : 'implement';
|
|
1472
|
+
}
|
|
1473
|
+
workItemSynced = true;
|
|
1476
1474
|
}
|
|
1477
|
-
|
|
1478
|
-
workItemSynced = true;
|
|
1479
|
-
}
|
|
1475
|
+
});
|
|
1480
1476
|
} catch (e) { console.error('work item sync:', e.message); }
|
|
1481
1477
|
}
|
|
1482
1478
|
|
|
@@ -1500,26 +1496,21 @@ const server = http.createServer(async (req, res) => {
|
|
|
1500
1496
|
|
|
1501
1497
|
// Also remove any materialized work item for this plan item
|
|
1502
1498
|
let cancelled = false;
|
|
1499
|
+
const allWiPaths = [path.join(MINIONS_DIR, 'work-items.json')];
|
|
1503
1500
|
for (const proj of PROJECTS) {
|
|
1504
|
-
|
|
1501
|
+
allWiPaths.push(shared.projectWorkItemsPath(proj));
|
|
1502
|
+
}
|
|
1503
|
+
for (const wiPath of allWiPaths) {
|
|
1505
1504
|
try {
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
}
|
|
1505
|
+
mutateWorkItems(wiPath, items => {
|
|
1506
|
+
const filtered = items.filter(w => !(w.sourcePlan === body.source && w.id === body.itemId));
|
|
1507
|
+
if (filtered.length < items.length) {
|
|
1508
|
+
cancelled = true;
|
|
1509
|
+
return filtered;
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1513
1512
|
} catch (e) { console.error('work item cleanup:', e.message); }
|
|
1514
1513
|
}
|
|
1515
|
-
// Also check central work-items
|
|
1516
|
-
const centralPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
1517
|
-
try {
|
|
1518
|
-
const items = safeJson(centralPath);
|
|
1519
|
-
const before = items.length;
|
|
1520
|
-
const filtered = items.filter(w => !(w.sourcePlan === body.source && w.id === body.itemId));
|
|
1521
|
-
if (filtered.length < before) { safeWrite(centralPath, filtered); cancelled = true; }
|
|
1522
|
-
} catch (e) { console.error('central work item cleanup:', e.message); }
|
|
1523
1514
|
|
|
1524
1515
|
// Clean dispatch entries for this item
|
|
1525
1516
|
cleanDispatchEntries(d =>
|
|
@@ -2130,52 +2121,51 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2130
2121
|
mutateJsonFileLocked(dispatchPath, (dispatch) => {
|
|
2131
2122
|
for (const wiPath of wiPaths) {
|
|
2132
2123
|
try {
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2124
|
+
mutateWorkItems(wiPath, items => {
|
|
2125
|
+
let changed = false;
|
|
2126
|
+
for (const w of items) {
|
|
2127
|
+
if (w.sourcePlan !== body.file) continue;
|
|
2128
|
+
// Keep completed items as-is, reset everything else to pending.
|
|
2129
|
+
if (w.completedAt || DONE_STATUSES.has(w.status)) continue;
|
|
2130
|
+
|
|
2131
|
+
if (w.status === WI_STATUS.DISPATCHED) {
|
|
2132
|
+
// Kill the agent working on this item, if any.
|
|
2133
|
+
const activeEntry = (dispatch.active || []).find(d => d.meta?.item?.id === w.id || d.meta?.dispatchKey?.includes(w.id));
|
|
2134
|
+
if (activeEntry) {
|
|
2135
|
+
const statusPath = path.join(MINIONS_DIR, 'agents', activeEntry.agent, 'status.json');
|
|
2136
|
+
try {
|
|
2137
|
+
const agentStatus = JSON.parse(safeRead(statusPath) || '{}');
|
|
2138
|
+
if (agentStatus.pid) {
|
|
2139
|
+
try {
|
|
2140
|
+
const safePid = shared.validatePid(agentStatus.pid);
|
|
2141
|
+
if (process.platform === 'win32') {
|
|
2142
|
+
require('child_process').execFileSync('taskkill', ['/PID', String(safePid), '/F', '/T'], { stdio: 'pipe', timeout: 5000, windowsHide: true });
|
|
2143
|
+
} else {
|
|
2144
|
+
process.kill(safePid, 'SIGTERM');
|
|
2145
|
+
}
|
|
2146
|
+
} catch { /* process may be dead or invalid PID */ }
|
|
2147
|
+
}
|
|
2148
|
+
agentStatus.status = 'idle';
|
|
2149
|
+
delete agentStatus.currentTask;
|
|
2150
|
+
delete agentStatus.dispatched;
|
|
2151
|
+
safeWrite(statusPath, agentStatus);
|
|
2152
|
+
} catch (e) { console.error('agent reset:', e.message); }
|
|
2153
|
+
killedAgents.add(activeEntry.agent);
|
|
2154
|
+
}
|
|
2164
2155
|
}
|
|
2165
|
-
}
|
|
2166
2156
|
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2157
|
+
if (w.status !== WI_STATUS.PAUSED) reset++;
|
|
2158
|
+
w.status = WI_STATUS.PAUSED;
|
|
2159
|
+
w._pausedBy = 'prd-pause';
|
|
2160
|
+
delete w._resumedAt;
|
|
2161
|
+
delete w.dispatched_at;
|
|
2162
|
+
delete w.dispatched_to;
|
|
2163
|
+
delete w.failReason;
|
|
2164
|
+
delete w.failedAt;
|
|
2165
|
+
changed = true;
|
|
2166
|
+
if (w.id) resetItemIds.add(w.id);
|
|
2167
|
+
}
|
|
2168
|
+
});
|
|
2179
2169
|
} catch (e) { console.error('reset work items:', e.message); }
|
|
2180
2170
|
}
|
|
2181
2171
|
|
|
@@ -2220,13 +2210,15 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2220
2210
|
const config = queries.getConfig();
|
|
2221
2211
|
for (const p of getProjects(config)) {
|
|
2222
2212
|
const projWiPath = projectWorkItemsPath(p);
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2213
|
+
try {
|
|
2214
|
+
mutateWorkItems(projWiPath, items => {
|
|
2215
|
+
const filtered = items.filter(w => {
|
|
2216
|
+
if (w.sourcePlan !== body.file) return true; // different plan, keep
|
|
2217
|
+
return completedStatuses.has(w.status); // keep completed, remove pending/failed
|
|
2218
|
+
});
|
|
2219
|
+
if (filtered.length < items.length) return filtered;
|
|
2220
|
+
});
|
|
2221
|
+
} catch { /* project may not have work items */ }
|
|
2230
2222
|
}
|
|
2231
2223
|
|
|
2232
2224
|
// Delete old PRD — agent will write replacement at same path
|
|
@@ -2234,30 +2226,29 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2234
2226
|
|
|
2235
2227
|
// Queue plan-to-prd regeneration with instructions to preserve completed items
|
|
2236
2228
|
const wiPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
2237
|
-
let items = [];
|
|
2238
|
-
const existing = safeRead(wiPath);
|
|
2239
|
-
if (existing) { try { items = JSON.parse(existing); } catch {} }
|
|
2240
|
-
|
|
2241
|
-
// Dedup: check if already queued
|
|
2242
|
-
const alreadyQueued = items.find(w =>
|
|
2243
|
-
w.type === 'plan-to-prd' && w.planFile === plan.source_plan && (w.status === 'pending' || w.status === 'dispatched')
|
|
2244
|
-
);
|
|
2245
|
-
if (alreadyQueued) return jsonReply(res, 200, { id: alreadyQueued.id, alreadyQueued: true });
|
|
2246
2229
|
|
|
2247
2230
|
const completedContext = completedItems.length > 0
|
|
2248
2231
|
? `\n\n**Previously completed items (preserve their status in the new PRD):**\n${completedItems.map(i => `- ${i.id}: ${i.name} [${i.status}]`).join('\n')}`
|
|
2249
2232
|
: '';
|
|
2250
2233
|
|
|
2251
2234
|
const id = 'W-' + shared.uid();
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2235
|
+
let alreadyQueuedId = null;
|
|
2236
|
+
mutateWorkItems(wiPath, items => {
|
|
2237
|
+
// Dedup: check if already queued
|
|
2238
|
+
const alreadyQueued = items.find(w =>
|
|
2239
|
+
w.type === 'plan-to-prd' && w.planFile === plan.source_plan && (w.status === 'pending' || w.status === 'dispatched')
|
|
2240
|
+
);
|
|
2241
|
+
if (alreadyQueued) { alreadyQueuedId = alreadyQueued.id; return; }
|
|
2242
|
+
items.push({
|
|
2243
|
+
id, title: `Regenerate PRD: ${plan.plan_summary || plan.source_plan}`,
|
|
2244
|
+
type: 'plan-to-prd', priority: 'high',
|
|
2245
|
+
description: `Plan file: plans/${plan.source_plan}\nTarget PRD filename: ${body.file}\nRegeneration requested by user after plan revision.${completedContext}`,
|
|
2246
|
+
status: 'pending', created: new Date().toISOString(), createdBy: 'dashboard:regenerate',
|
|
2247
|
+
project: plan.project || '', planFile: plan.source_plan,
|
|
2248
|
+
_targetPrdFile: body.file,
|
|
2249
|
+
});
|
|
2259
2250
|
});
|
|
2260
|
-
|
|
2251
|
+
if (alreadyQueuedId) return jsonReply(res, 200, { id: alreadyQueuedId, alreadyQueued: true });
|
|
2261
2252
|
return jsonReply(res, 200, { id, file: plan.source_plan });
|
|
2262
2253
|
} catch (e) { return jsonReply(res, 500, { error: e.message }); }
|
|
2263
2254
|
}
|
|
@@ -2334,27 +2325,26 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2334
2325
|
|
|
2335
2326
|
for (const wiInfo of wiPaths) {
|
|
2336
2327
|
try {
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2328
|
+
mutateWorkItems(wiInfo.path, items => {
|
|
2329
|
+
const filtered = [];
|
|
2330
|
+
for (const w of items) {
|
|
2331
|
+
if (w.sourcePlan === body.source) {
|
|
2332
|
+
materializedPlanItemIds.add(w.id);
|
|
2333
|
+
if (w.status === 'pending' || w.status === 'failed') {
|
|
2334
|
+
// Delete — will re-materialize on next tick with updated plan data
|
|
2335
|
+
reset++;
|
|
2336
|
+
deletedItemIds.push(w.id);
|
|
2337
|
+
} else {
|
|
2338
|
+
// dispatched or done — leave alone
|
|
2339
|
+
kept++;
|
|
2340
|
+
filtered.push(w);
|
|
2341
|
+
}
|
|
2346
2342
|
} else {
|
|
2347
|
-
// dispatched or done — leave alone
|
|
2348
|
-
kept++;
|
|
2349
2343
|
filtered.push(w);
|
|
2350
2344
|
}
|
|
2351
|
-
} else {
|
|
2352
|
-
filtered.push(w);
|
|
2353
2345
|
}
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
safeWrite(wiInfo.path, filtered);
|
|
2357
|
-
}
|
|
2346
|
+
if (filtered.length < items.length) return filtered;
|
|
2347
|
+
});
|
|
2358
2348
|
} catch (e) { console.error('work item sync:', e.message); }
|
|
2359
2349
|
}
|
|
2360
2350
|
|
|
@@ -2396,13 +2386,13 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2396
2386
|
}
|
|
2397
2387
|
for (const wiPath of wiPaths) {
|
|
2398
2388
|
try {
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
}
|
|
2389
|
+
mutateWorkItems(wiPath, items => {
|
|
2390
|
+
const filtered = items.filter(w => w.sourcePlan !== body.file);
|
|
2391
|
+
if (filtered.length < items.length) {
|
|
2392
|
+
cleaned += items.length - filtered.length;
|
|
2393
|
+
return filtered;
|
|
2394
|
+
}
|
|
2395
|
+
});
|
|
2406
2396
|
} catch (e) { console.error('plan cleanup:', e.message); }
|
|
2407
2397
|
}
|
|
2408
2398
|
|
|
@@ -2417,16 +2407,14 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2417
2407
|
if (prdSourcePlan) {
|
|
2418
2408
|
try {
|
|
2419
2409
|
const centralPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
changed = true;
|
|
2410
|
+
mutateWorkItems(centralPath, items => {
|
|
2411
|
+
for (const w of items) {
|
|
2412
|
+
if (w.type === 'plan-to-prd' && w.status === 'done' && w.planFile === prdSourcePlan) {
|
|
2413
|
+
w.status = 'cancelled';
|
|
2414
|
+
w._cancelledBy = 'prd-deleted';
|
|
2415
|
+
}
|
|
2427
2416
|
}
|
|
2428
|
-
}
|
|
2429
|
-
if (changed) safeWrite(centralPath, centralItems);
|
|
2417
|
+
});
|
|
2430
2418
|
} catch (e) { console.error('plan-to-prd cleanup:', e.message); }
|
|
2431
2419
|
}
|
|
2432
2420
|
|
|
@@ -2521,19 +2509,17 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2521
2509
|
|
|
2522
2510
|
// Create a work item to revise the plan
|
|
2523
2511
|
const wiPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
2524
|
-
let items = [];
|
|
2525
|
-
const existing = safeRead(wiPath);
|
|
2526
|
-
if (existing) { try { items = JSON.parse(existing); } catch {} }
|
|
2527
2512
|
const id = 'W-' + shared.uid();
|
|
2528
|
-
items
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2513
|
+
mutateWorkItems(wiPath, items => {
|
|
2514
|
+
items.push({
|
|
2515
|
+
id, title: 'Revise plan: ' + (plan.plan_summary || body.file),
|
|
2516
|
+
type: 'plan-to-prd', priority: 'high',
|
|
2517
|
+
description: 'Revision requested on plan file: ' + (body.file.endsWith('.json') ? 'prd/' : 'plans/') + body.file + '\n\nFeedback:\n' + body.feedback + '\n\nRevise the plan to address this feedback. Read the existing plan, apply the feedback, and overwrite the file with the updated version. Set status back to "awaiting-approval".',
|
|
2518
|
+
status: 'pending', created: new Date().toISOString(), createdBy: 'dashboard:revision',
|
|
2519
|
+
project: plan.project || '',
|
|
2520
|
+
planFile: body.file,
|
|
2521
|
+
});
|
|
2535
2522
|
});
|
|
2536
|
-
safeWrite(wiPath, items);
|
|
2537
2523
|
return jsonReply(res, 200, { ok: true, status: 'revision-requested', workItemId: id });
|
|
2538
2524
|
} catch (e) { return jsonReply(res, 400, { error: e.message }); }
|
|
2539
2525
|
}
|
|
@@ -2624,22 +2610,23 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2624
2610
|
const deletedItemIds = [];
|
|
2625
2611
|
for (const wiInfo of wiPaths) {
|
|
2626
2612
|
try {
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2613
|
+
mutateWorkItems(wiInfo.path, items => {
|
|
2614
|
+
const filtered = [];
|
|
2615
|
+
for (const w of items) {
|
|
2616
|
+
if (w.sourcePlan === body.source) {
|
|
2617
|
+
if (w.status === 'pending' || w.status === 'failed') {
|
|
2618
|
+
reset++;
|
|
2619
|
+
deletedItemIds.push(w.id);
|
|
2620
|
+
} else {
|
|
2621
|
+
kept++;
|
|
2622
|
+
filtered.push(w);
|
|
2623
|
+
}
|
|
2634
2624
|
} else {
|
|
2635
|
-
kept++;
|
|
2636
2625
|
filtered.push(w);
|
|
2637
2626
|
}
|
|
2638
|
-
} else {
|
|
2639
|
-
filtered.push(w);
|
|
2640
2627
|
}
|
|
2641
|
-
|
|
2642
|
-
|
|
2628
|
+
if (filtered.length < items.length) return filtered;
|
|
2629
|
+
});
|
|
2643
2630
|
} catch (e) { console.error('work item deletion:', e.message); }
|
|
2644
2631
|
}
|
|
2645
2632
|
for (const itemId of deletedItemIds) {
|
|
@@ -2650,22 +2637,21 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2650
2637
|
|
|
2651
2638
|
// Step 4: Dispatch plan-to-prd to regenerate PRD from revised plan
|
|
2652
2639
|
const centralWiPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
2653
|
-
let centralItems = [];
|
|
2654
|
-
try { centralItems = JSON.parse(safeRead(centralWiPath) || '[]'); } catch {}
|
|
2655
2640
|
const wiId = 'W-' + shared.uid();
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2641
|
+
mutateWorkItems(centralWiPath, items => {
|
|
2642
|
+
items.push({
|
|
2643
|
+
id: wiId,
|
|
2644
|
+
title: 'Regenerate PRD from revised plan: ' + sourcePlanFile,
|
|
2645
|
+
type: 'plan-to-prd',
|
|
2646
|
+
priority: 'high',
|
|
2647
|
+
description: `The source plan \`${sourcePlanFile}\` has been revised. Convert it into a fresh PRD JSON.\n\nRevision instruction: ${body.instruction}\n\nRead the revised plan, generate updated PRD items (missing_features), and write to \`prd/${body.source}\`. Set status to "approved". Include \`"source_plan": "${sourcePlanFile}"\` in the JSON root.\n\nPreserve items that are already done (status "implemented" or "complete"). Reset or replace items that were pending/failed.`,
|
|
2648
|
+
status: 'pending',
|
|
2649
|
+
created: new Date().toISOString(),
|
|
2650
|
+
createdBy: 'dashboard:revise-and-regenerate',
|
|
2651
|
+
project: prd.project || '',
|
|
2652
|
+
planFile: sourcePlanFile,
|
|
2653
|
+
});
|
|
2667
2654
|
});
|
|
2668
|
-
safeWrite(centralWiPath, centralItems);
|
|
2669
2655
|
|
|
2670
2656
|
return jsonReply(res, 200, {
|
|
2671
2657
|
ok: true,
|
|
@@ -3728,24 +3714,27 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3728
3714
|
const projects = shared.getProjects(CONFIG);
|
|
3729
3715
|
const paths = [path.join(MINIONS_DIR, 'work-items.json')];
|
|
3730
3716
|
for (const p of projects) paths.push(shared.projectWorkItemsPath(p));
|
|
3717
|
+
let found = null;
|
|
3731
3718
|
for (const wiPath of paths) {
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
'**Item:** ' + (item.title || id) + '\n' +
|
|
3741
|
-
'**Agent:** ' + agent + '\n' +
|
|
3742
|
-
(comment ? '**Feedback:** ' + comment + '\n' : '');
|
|
3743
|
-
const inboxPath = path.join(MINIONS_DIR, 'notes', 'inbox', agent + '-feedback-' + new Date().toISOString().slice(0, 10) + '-' + shared.uid().slice(0, 4) + '.md');
|
|
3744
|
-
safeWrite(inboxPath, feedbackNote);
|
|
3745
|
-
invalidateStatusCache();
|
|
3746
|
-
return jsonReply(res, 200, { ok: true });
|
|
3719
|
+
mutateWorkItems(wiPath, items => {
|
|
3720
|
+
const item = items.find(i => i.id === id);
|
|
3721
|
+
if (item && !found) {
|
|
3722
|
+
item._humanFeedback = { rating, comment: comment || '', at: new Date().toISOString() };
|
|
3723
|
+
found = { agent: item.dispatched_to || item.agent || 'unknown', title: item.title || id };
|
|
3724
|
+
}
|
|
3725
|
+
});
|
|
3726
|
+
if (found) break;
|
|
3747
3727
|
}
|
|
3748
|
-
return jsonReply(res, 404, { error: 'Work item not found' });
|
|
3728
|
+
if (!found) return jsonReply(res, 404, { error: 'Work item not found' });
|
|
3729
|
+
const feedbackNote = '# Human Feedback on ' + id + '\n\n' +
|
|
3730
|
+
'**Rating:** ' + (rating === 'up' ? '👍 Good' : '👎 Needs improvement') + '\n' +
|
|
3731
|
+
'**Item:** ' + found.title + '\n' +
|
|
3732
|
+
'**Agent:** ' + found.agent + '\n' +
|
|
3733
|
+
(comment ? '**Feedback:** ' + comment + '\n' : '');
|
|
3734
|
+
const inboxPath = path.join(MINIONS_DIR, 'notes', 'inbox', found.agent + '-feedback-' + new Date().toISOString().slice(0, 10) + '-' + shared.uid().slice(0, 4) + '.md');
|
|
3735
|
+
safeWrite(inboxPath, feedbackNote);
|
|
3736
|
+
invalidateStatusCache();
|
|
3737
|
+
return jsonReply(res, 200, { ok: true });
|
|
3749
3738
|
}},
|
|
3750
3739
|
|
|
3751
3740
|
// Pinned notes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.532",
|
|
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"
|