@yemi33/minions 0.1.11 → 0.1.13
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 +60 -0
- package/dashboard/js/command-center.js +377 -0
- package/dashboard/js/command-history.js +70 -0
- package/dashboard/js/command-input.js +268 -0
- package/dashboard/js/command-parser.js +129 -0
- package/dashboard/js/detail-panel.js +98 -0
- package/dashboard/js/live-stream.js +69 -0
- package/dashboard/js/modal-qa.js +268 -0
- package/dashboard/js/modal.js +131 -0
- package/dashboard/js/refresh.js +59 -0
- package/dashboard/js/render-agents.js +17 -0
- package/dashboard/js/render-dispatch.js +148 -0
- package/dashboard/js/render-inbox.js +126 -0
- package/dashboard/js/render-kb.js +107 -0
- package/dashboard/js/render-other.js +181 -0
- package/dashboard/js/render-plans.js +304 -0
- package/dashboard/js/render-prd.js +469 -0
- package/dashboard/js/render-prs.js +94 -0
- package/dashboard/js/render-schedules.js +158 -0
- package/dashboard/js/render-skills.js +89 -0
- package/dashboard/js/render-work-items.js +219 -0
- package/dashboard/js/settings.js +135 -0
- package/dashboard/js/state.js +84 -0
- package/dashboard/js/utils.js +39 -0
- package/dashboard/layout.html +123 -0
- package/dashboard/pages/engine.html +12 -0
- package/dashboard/pages/home.html +31 -0
- package/dashboard/pages/inbox.html +17 -0
- package/dashboard/pages/plans.html +4 -0
- package/dashboard/pages/prd.html +5 -0
- package/dashboard/pages/prs.html +4 -0
- package/dashboard/pages/schedule.html +10 -0
- package/dashboard/pages/work.html +5 -0
- package/dashboard/styles.css +598 -0
- package/dashboard-build.js +51 -0
- package/dashboard.html +179 -107
- package/dashboard.js +51 -1
- package/engine/ado.js +14 -0
- package/engine/cli.js +11 -0
- package/engine/github.js +14 -0
- package/engine/lifecycle.js +25 -29
- package/engine.js +106 -19
- package/package.json +1 -1
- package/routing.md +1 -1
package/engine.js
CHANGED
|
@@ -166,6 +166,25 @@ function getRoutingTableCached() {
|
|
|
166
166
|
return _routingCache;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
function getMonthlySpend(agentId) {
|
|
170
|
+
const metrics = safeJson(path.join(ENGINE_DIR, 'metrics.json')) || {};
|
|
171
|
+
const daily = metrics._daily || {};
|
|
172
|
+
const now = new Date();
|
|
173
|
+
const monthPrefix = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
|
174
|
+
let total = 0;
|
|
175
|
+
for (const [date, data] of Object.entries(daily)) {
|
|
176
|
+
if (date.startsWith(monthPrefix)) {
|
|
177
|
+
total += (data.perAgent?.[agentId]?.costUsd || 0);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Fallback: if no per-agent daily data, use cumulative (less accurate for monthly)
|
|
181
|
+
if (total === 0 && metrics[agentId]?.totalCostUsd) {
|
|
182
|
+
// Can't distinguish monthly from cumulative — treat as monthly estimate
|
|
183
|
+
// This path is for backward compat before per-agent daily tracking was added
|
|
184
|
+
}
|
|
185
|
+
return total;
|
|
186
|
+
}
|
|
187
|
+
|
|
169
188
|
function getAgentErrorRate(agentId) {
|
|
170
189
|
const metricsPath = path.join(ENGINE_DIR, 'metrics.json');
|
|
171
190
|
const metrics = safeJson(metricsPath) || {};
|
|
@@ -194,7 +213,15 @@ function resolveAgent(workType, config, authorAgent = null) {
|
|
|
194
213
|
let preferred = route.preferred === '_author_' ? authorAgent : route.preferred;
|
|
195
214
|
let fallback = route.fallback === '_author_' ? authorAgent : route.fallback;
|
|
196
215
|
|
|
197
|
-
const isAvailable = (id) =>
|
|
216
|
+
const isAvailable = (id) => {
|
|
217
|
+
if (!agents[id] || !isAgentIdle(id) || _claimedAgents.has(id)) return false;
|
|
218
|
+
// Budget check — no budget means infinite (no limit)
|
|
219
|
+
const budget = agents[id].monthlyBudgetUsd;
|
|
220
|
+
if (budget && budget > 0) {
|
|
221
|
+
if (getMonthlySpend(id) >= budget) return false;
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
};
|
|
198
225
|
|
|
199
226
|
// Check preferred and fallback first (routing table order)
|
|
200
227
|
if (preferred && isAvailable(preferred)) { _claimedAgents.add(preferred); return preferred; }
|
|
@@ -905,6 +932,20 @@ function spawnAgent(dispatchItem, config) {
|
|
|
905
932
|
args.push('--allowedTools', claudeConfig.allowedTools);
|
|
906
933
|
}
|
|
907
934
|
|
|
935
|
+
// Session resume: reuse last session if recent enough (< 2 hours)
|
|
936
|
+
if (!agentId.startsWith('temp-')) {
|
|
937
|
+
try {
|
|
938
|
+
const sessionFile = safeJson(path.join(AGENTS_DIR, agentId, 'session.json'));
|
|
939
|
+
if (sessionFile?.sessionId && sessionFile.savedAt) {
|
|
940
|
+
const sessionAge = Date.now() - new Date(sessionFile.savedAt).getTime();
|
|
941
|
+
if (sessionAge < 2 * 60 * 60 * 1000) { // 2 hour TTL
|
|
942
|
+
args.push('--resume', sessionFile.sessionId);
|
|
943
|
+
log('info', `Resuming session ${sessionFile.sessionId} for ${agentId} (age: ${Math.round(sessionAge / 60000)}min)`);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
} catch {}
|
|
947
|
+
}
|
|
948
|
+
|
|
908
949
|
// MCP servers: agents inherit from ~/.claude.json directly as Claude Code processes.
|
|
909
950
|
// No --mcp-config needed — avoids redundant config and ensures agents always have latest servers.
|
|
910
951
|
|
|
@@ -1968,6 +2009,27 @@ function setCooldown(key) {
|
|
|
1968
2009
|
saveCooldowns();
|
|
1969
2010
|
}
|
|
1970
2011
|
|
|
2012
|
+
function setCooldownWithContext(key, context) {
|
|
2013
|
+
const existing = dispatchCooldowns.get(key);
|
|
2014
|
+
const pendingContexts = existing?.pendingContexts || [];
|
|
2015
|
+
if (context) pendingContexts.push(context);
|
|
2016
|
+
dispatchCooldowns.set(key, {
|
|
2017
|
+
timestamp: Date.now(),
|
|
2018
|
+
failures: existing?.failures || 0,
|
|
2019
|
+
pendingContexts
|
|
2020
|
+
});
|
|
2021
|
+
saveCooldowns();
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
function getCoalescedContexts(key) {
|
|
2025
|
+
const entry = dispatchCooldowns.get(key);
|
|
2026
|
+
const contexts = entry?.pendingContexts || [];
|
|
2027
|
+
if (contexts.length > 0 && entry) {
|
|
2028
|
+
entry.pendingContexts = []; // Clear after retrieval
|
|
2029
|
+
}
|
|
2030
|
+
return contexts;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
1971
2033
|
function setCooldownFailure(key) {
|
|
1972
2034
|
const existing = dispatchCooldowns.get(key);
|
|
1973
2035
|
const failures = (existing?.failures || 0) + 1;
|
|
@@ -2390,19 +2452,16 @@ function discoverFromPrs(config, project) {
|
|
|
2390
2452
|
if (activePrIds.has(pr.id)) continue; // Skip PRs with active dispatch (prevent race)
|
|
2391
2453
|
|
|
2392
2454
|
const prNumber = (pr.id || '').replace(/^PR-/, '');
|
|
2393
|
-
|
|
2455
|
+
// Use reviewStatus as single source of truth (synced from ADO/GitHub votes)
|
|
2456
|
+
// minionsReview tracks metadata (reviewer, note) but not the authoritative status
|
|
2457
|
+
const reviewStatus = pr.reviewStatus || 'pending';
|
|
2394
2458
|
|
|
2395
|
-
// PRs needing review
|
|
2396
|
-
const needsReview =
|
|
2459
|
+
// PRs needing review: pending or waiting (review dispatched but no verdict yet)
|
|
2460
|
+
const needsReview = reviewStatus === 'pending' || reviewStatus === 'waiting';
|
|
2397
2461
|
if (needsReview) {
|
|
2398
2462
|
const key = `review-${project?.name || 'default'}-${pr.id}`;
|
|
2399
2463
|
if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
|
|
2400
|
-
|
|
2401
|
-
const prAuthor = (pr.agent || '').toLowerCase();
|
|
2402
|
-
let agentId = resolveAgent('review', config);
|
|
2403
|
-
if (agentId && agentId === prAuthor) {
|
|
2404
|
-
agentId = resolveAgent('review', config); // retry — prAuthor now claimed, gets skipped
|
|
2405
|
-
}
|
|
2464
|
+
const agentId = resolveAgent('review', config);
|
|
2406
2465
|
if (!agentId) continue;
|
|
2407
2466
|
|
|
2408
2467
|
const item = buildPrDispatch(agentId, config, project, pr, 'review', {
|
|
@@ -2413,7 +2472,7 @@ function discoverFromPrs(config, project) {
|
|
|
2413
2472
|
}
|
|
2414
2473
|
|
|
2415
2474
|
// PRs with changes requested → route back to author for fix
|
|
2416
|
-
if (
|
|
2475
|
+
if (reviewStatus === 'changes-requested') {
|
|
2417
2476
|
const key = `fix-${project?.name || 'default'}-${pr.id}`;
|
|
2418
2477
|
if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
|
|
2419
2478
|
const agentId = resolveAgent('fix', config, pr.agent);
|
|
@@ -2426,22 +2485,37 @@ function discoverFromPrs(config, project) {
|
|
|
2426
2485
|
if (item) { newWork.push(item); setCooldown(key); }
|
|
2427
2486
|
}
|
|
2428
2487
|
|
|
2429
|
-
// PRs with pending human feedback
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2488
|
+
// PRs with pending human feedback (or coalesced comments from while agent was fixing)
|
|
2489
|
+
const humanFixKey = `human-fix-${project?.name || 'default'}-${pr.id}`;
|
|
2490
|
+
const hasCoalescedFeedback = (dispatchCooldowns.get(humanFixKey)?.pendingContexts || []).length > 0;
|
|
2491
|
+
if (pr.humanFeedback?.pendingFix || hasCoalescedFeedback) {
|
|
2492
|
+
const key = humanFixKey;
|
|
2493
|
+
if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) {
|
|
2494
|
+
// Coalesce: save feedback for next dispatch
|
|
2495
|
+
if (pr.humanFeedback?.feedbackContent) {
|
|
2496
|
+
setCooldownWithContext(key, { feedbackContent: pr.humanFeedback.feedbackContent, timestamp: new Date().toISOString() });
|
|
2497
|
+
}
|
|
2498
|
+
continue;
|
|
2499
|
+
}
|
|
2433
2500
|
const agentId = resolveAgent('fix', config, pr.agent);
|
|
2434
2501
|
if (!agentId) continue;
|
|
2435
2502
|
|
|
2503
|
+
const coalesced = getCoalescedContexts(key);
|
|
2504
|
+
let reviewNote = pr.humanFeedback.feedbackContent || 'See PR thread comments';
|
|
2505
|
+
if (coalesced.length > 0) {
|
|
2506
|
+
const earlier = coalesced.map(c => c.feedbackContent).filter(Boolean).join('\n\n---\n\n');
|
|
2507
|
+
if (earlier) reviewNote = earlier + '\n\n---\n\n' + reviewNote;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2436
2510
|
const item = buildPrDispatch(agentId, config, project, pr, 'fix', {
|
|
2437
2511
|
pr_id: pr.id, pr_number: prNumber, pr_title: pr.title || '', pr_branch: pr.branch || '',
|
|
2438
2512
|
reviewer: 'Human Reviewer',
|
|
2439
|
-
review_note:
|
|
2513
|
+
review_note: reviewNote,
|
|
2440
2514
|
}, `Fix PR ${pr.id} — human feedback`, { dispatchKey: key, source: 'pr-human-feedback', pr, branch: pr.branch, project: projMeta });
|
|
2441
2515
|
if (item) { newWork.push(item); setCooldown(key); }
|
|
2442
2516
|
}
|
|
2443
2517
|
|
|
2444
|
-
// PRs with build failures
|
|
2518
|
+
// PRs with build failures — any agent can pick this up
|
|
2445
2519
|
if (pr.status === 'active' && pr.buildStatus === 'failing') {
|
|
2446
2520
|
const key = `build-fix-${project?.name || 'default'}-${pr.id}`;
|
|
2447
2521
|
if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
|
|
@@ -2565,7 +2639,17 @@ function discoverFromWorkItems(config, project) {
|
|
|
2565
2639
|
}
|
|
2566
2640
|
const agentId = item.agent || resolveAgent(workType, config);
|
|
2567
2641
|
if (!agentId) {
|
|
2568
|
-
|
|
2642
|
+
// Check if reason is budget
|
|
2643
|
+
const cfgAgents = config.agents || {};
|
|
2644
|
+
const budgetBlocked = Object.keys(cfgAgents).some(id => {
|
|
2645
|
+
const b = cfgAgents[id].monthlyBudgetUsd;
|
|
2646
|
+
return b && b > 0 && getMonthlySpend(id) >= b && isAgentIdle(id);
|
|
2647
|
+
});
|
|
2648
|
+
if (budgetBlocked) {
|
|
2649
|
+
if (item._pendingReason !== 'budget_exceeded') { item._pendingReason = 'budget_exceeded'; needsWrite = true; }
|
|
2650
|
+
} else {
|
|
2651
|
+
if (item._pendingReason !== 'no_agent') { item._pendingReason = 'no_agent'; needsWrite = true; }
|
|
2652
|
+
}
|
|
2569
2653
|
skipped.noAgent++; continue;
|
|
2570
2654
|
}
|
|
2571
2655
|
|
|
@@ -3396,7 +3480,10 @@ module.exports = {
|
|
|
3396
3480
|
updateWorkItemStatus, runCleanup, handlePostMerge,
|
|
3397
3481
|
|
|
3398
3482
|
// Cooldowns
|
|
3399
|
-
loadCooldowns,
|
|
3483
|
+
loadCooldowns, setCooldownWithContext, getCoalescedContexts,
|
|
3484
|
+
|
|
3485
|
+
// Budget
|
|
3486
|
+
getMonthlySpend,
|
|
3400
3487
|
|
|
3401
3488
|
// Tick
|
|
3402
3489
|
tick,
|
package/package.json
CHANGED
package/routing.md
CHANGED
|
@@ -27,7 +27,7 @@ Notes:
|
|
|
27
27
|
## Rules
|
|
28
28
|
|
|
29
29
|
1. **Eager by default** — spawn all agents who can start work, not one at a time
|
|
30
|
-
2. **
|
|
30
|
+
2. **Self-review is allowed** — agents can review their own PRs (useful for single-agent setups)
|
|
31
31
|
3. **Exploration gates implementation** — when exploring, finish before implementing
|
|
32
32
|
4. **Implementation informs PRD** — Lambert reads build summaries before writing PRD
|
|
33
33
|
5. **All rules in `notes.md` apply** — engine injects them into every playbook
|