@yemi33/minions 0.1.1665 → 0.1.1666
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 -6
- package/engine/copilot-models.json +1 -1
- package/engine.js +82 -50
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.1666 (2026-05-01)
|
|
4
|
+
|
|
5
|
+
### Other
|
|
6
|
+
- Fix soft routing for fix dispatches
|
|
7
|
+
|
|
8
|
+
## 0.1.1664 (2026-05-01)
|
|
4
9
|
|
|
5
10
|
### Features
|
|
6
|
-
- treat failed task_complete as dispatch failure (#1954)
|
|
7
11
|
- prevent duplicate PR fix dispatch (#1953)
|
|
8
12
|
|
|
9
|
-
### Fixes
|
|
10
|
-
- pre-dispatch agent starvation when only one agent is idle (closes #1940) (#1956)
|
|
11
|
-
- Engine clean-exit agent runs incorrectly marked Orphaned/silent (#1955)
|
|
12
|
-
|
|
13
13
|
## 0.1.1662 (2026-05-01)
|
|
14
14
|
|
|
15
15
|
### Other
|
package/engine.js
CHANGED
|
@@ -2753,7 +2753,7 @@ function discoverFromWorkItems(config, project) {
|
|
|
2753
2753
|
return b && b > 0 && getMonthlySpend(id) >= b && isAgentIdle(id);
|
|
2754
2754
|
});
|
|
2755
2755
|
if (!agentId) {
|
|
2756
|
-
if (!budgetBlocked && !hardPinRequested) {
|
|
2756
|
+
if (!budgetBlocked && !hardPinRequested && workType !== WORK_TYPE.FIX) {
|
|
2757
2757
|
reservedAgentId = resolveAgentReservation(workType, config, { agentHints });
|
|
2758
2758
|
agentId = reservedAgentId && !hasAgentHints ? routing.ANY_AGENT : reservedAgentId;
|
|
2759
2759
|
}
|
|
@@ -3253,7 +3253,7 @@ function discoverCentralWorkItems(config) {
|
|
|
3253
3253
|
const hardPinRequested = routing.isAgentHardPinned(item);
|
|
3254
3254
|
const agentId = routing.getHardPinnedAgent(item, config.agents || {})
|
|
3255
3255
|
|| (!hardPinRequested ? resolveAgent(workType, config, { agentHints }) : null)
|
|
3256
|
-
|| (!hardPinRequested ? resolveAgentReservation(workType, config, { agentHints }) : null);
|
|
3256
|
+
|| (!hardPinRequested && workType !== WORK_TYPE.FIX ? resolveAgentReservation(workType, config, { agentHints }) : null);
|
|
3257
3257
|
if (!agentId) continue;
|
|
3258
3258
|
|
|
3259
3259
|
const agentName = config.agents[agentId]?.name || agentId;
|
|
@@ -3561,6 +3561,62 @@ async function discoverWork(config) {
|
|
|
3561
3561
|
return allWork.length;
|
|
3562
3562
|
}
|
|
3563
3563
|
|
|
3564
|
+
function getPendingDispatchRoutingOpts(item) {
|
|
3565
|
+
const opts = { agentHints: routing.extractAgentHints(item?.meta?.item) };
|
|
3566
|
+
const authorAgent = item?.meta?.pr?.agent;
|
|
3567
|
+
if (authorAgent) opts.authorAgent = authorAgent;
|
|
3568
|
+
return opts;
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
function resolvePendingDispatchAgent(item, config) {
|
|
3572
|
+
return resolveAgent(
|
|
3573
|
+
routing.normalizeWorkType(item?.type, WORK_TYPE.IMPLEMENT),
|
|
3574
|
+
config,
|
|
3575
|
+
getPendingDispatchRoutingOpts(item)
|
|
3576
|
+
);
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
function assignPendingDispatchAgent(item, agentId, config) {
|
|
3580
|
+
const agents = config.agents || {};
|
|
3581
|
+
item.agent = agentId;
|
|
3582
|
+
item.agentName = agents[agentId]?.name || tempAgents.get(agentId)?.name || agentId;
|
|
3583
|
+
item.agentRole = agents[agentId]?.role || tempAgents.get(agentId)?.role || 'Agent';
|
|
3584
|
+
delete item._agentBusySince;
|
|
3585
|
+
delete item.skipReason;
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
function clearPendingDispatchAgent(item) {
|
|
3589
|
+
delete item.agent;
|
|
3590
|
+
delete item.agentName;
|
|
3591
|
+
delete item.agentRole;
|
|
3592
|
+
delete item._agentBusySince;
|
|
3593
|
+
delete item.skipReason;
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
function persistPendingDispatchAgent(item) {
|
|
3597
|
+
mutateDispatch((dp) => {
|
|
3598
|
+
const p = (dp.pending || []).find(d => d.id === item.id);
|
|
3599
|
+
if (p) {
|
|
3600
|
+
if (item.agent) {
|
|
3601
|
+
p.agent = item.agent;
|
|
3602
|
+
p.agentName = item.agentName;
|
|
3603
|
+
p.agentRole = item.agentRole;
|
|
3604
|
+
} else {
|
|
3605
|
+
delete p.agent;
|
|
3606
|
+
delete p.agentName;
|
|
3607
|
+
delete p.agentRole;
|
|
3608
|
+
}
|
|
3609
|
+
delete p._agentBusySince;
|
|
3610
|
+
delete p.skipReason;
|
|
3611
|
+
}
|
|
3612
|
+
return dp;
|
|
3613
|
+
});
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
function isSoftFixDispatch(item) {
|
|
3617
|
+
return item?.type === WORK_TYPE.FIX && !routing.isAgentHardPinned(item.meta?.item);
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3564
3620
|
// ─── Main Tick ──────────────────────────────────────────────────────────────
|
|
3565
3621
|
|
|
3566
3622
|
let tickCount = 0;
|
|
@@ -3955,27 +4011,17 @@ async function tickInner() {
|
|
|
3955
4011
|
// be of type string. Received undefined` and re-queues — every tick. Try to
|
|
3956
4012
|
// resolve a fallback via routing; if none is available, skip this tick.
|
|
3957
4013
|
if (!item.agent || typeof item.agent !== 'string') {
|
|
3958
|
-
const fallback =
|
|
4014
|
+
const fallback = resolvePendingDispatchAgent(item, config);
|
|
3959
4015
|
if (!fallback) {
|
|
3960
4016
|
log('warn', `Pending dispatch ${item.id} has no agent and routing returned no fallback — skipping`);
|
|
3961
4017
|
continue;
|
|
3962
4018
|
}
|
|
3963
4019
|
log('info', `Pending dispatch ${item.id} missing agent; routed → ${fallback} (#1206 guard)`);
|
|
3964
|
-
item
|
|
3965
|
-
item.agentName = config.agents[fallback]?.name || tempAgents.get(fallback)?.name || fallback;
|
|
3966
|
-
item.agentRole = config.agents[fallback]?.role || tempAgents.get(fallback)?.role || 'Agent';
|
|
4020
|
+
assignPendingDispatchAgent(item, fallback, config);
|
|
3967
4021
|
// Persist so the fix survives across ticks even if this dispatch is skipped
|
|
3968
4022
|
// later in the loop (branch lock, concurrency cap, agent busy, etc.).
|
|
3969
4023
|
try {
|
|
3970
|
-
|
|
3971
|
-
const p = (dp.pending || []).find(d => d.id === item.id);
|
|
3972
|
-
if (p) {
|
|
3973
|
-
p.agent = item.agent;
|
|
3974
|
-
p.agentName = item.agentName;
|
|
3975
|
-
p.agentRole = item.agentRole;
|
|
3976
|
-
}
|
|
3977
|
-
return dp;
|
|
3978
|
-
});
|
|
4024
|
+
persistPendingDispatchAgent(item);
|
|
3979
4025
|
} catch (e) { log('warn', `Persist agent resolution for ${item.id} failed: ${e.message}`); }
|
|
3980
4026
|
}
|
|
3981
4027
|
// #1204: Pre-assigned unspawned temp agents never unblock naturally.
|
|
@@ -3986,27 +4032,13 @@ async function tickInner() {
|
|
|
3986
4032
|
// them eagerly before the busy check so an idle named agent can pick up.
|
|
3987
4033
|
const isUnspawnedTemp = item.agent?.startsWith('temp-') && !busyAgents.has(item.agent);
|
|
3988
4034
|
if (isUnspawnedTemp) {
|
|
3989
|
-
const altAgent =
|
|
4035
|
+
const altAgent = resolvePendingDispatchAgent(item, config);
|
|
3990
4036
|
if (altAgent && altAgent !== item.agent) {
|
|
3991
4037
|
const prevAgent = item.agent;
|
|
3992
|
-
item
|
|
3993
|
-
item.agentName = config.agents[altAgent]?.name || tempAgents.get(altAgent)?.name || altAgent;
|
|
3994
|
-
item.agentRole = config.agents[altAgent]?.role || tempAgents.get(altAgent)?.role || 'Agent';
|
|
3995
|
-
delete item._agentBusySince;
|
|
3996
|
-
delete item.skipReason;
|
|
4038
|
+
assignPendingDispatchAgent(item, altAgent, config);
|
|
3997
4039
|
log('info', `Reassigning ${item.id} from unspawned temp ${prevAgent} to ${altAgent} — temp agent never spawned`);
|
|
3998
4040
|
// Persist reassignment to dispatch.json so it survives restarts/ticks
|
|
3999
|
-
|
|
4000
|
-
const p = (dp.pending || []).find(d => d.id === item.id);
|
|
4001
|
-
if (p) {
|
|
4002
|
-
p.agent = altAgent;
|
|
4003
|
-
p.agentName = item.agentName;
|
|
4004
|
-
p.agentRole = item.agentRole;
|
|
4005
|
-
delete p._agentBusySince;
|
|
4006
|
-
delete p.skipReason;
|
|
4007
|
-
}
|
|
4008
|
-
return dp;
|
|
4009
|
-
});
|
|
4041
|
+
persistPendingDispatchAgent(item);
|
|
4010
4042
|
}
|
|
4011
4043
|
}
|
|
4012
4044
|
if (busyAgents.has(item.agent)) {
|
|
@@ -4014,30 +4046,30 @@ async function tickInner() {
|
|
|
4014
4046
|
// try to find an alternative agent via routing. Skip explicitly assigned items.
|
|
4015
4047
|
const reassignMs = config.engine?.agentBusyReassignMs ?? ENGINE_DEFAULTS.agentBusyReassignMs;
|
|
4016
4048
|
const isHardPinned = routing.isAgentHardPinned(item.meta?.item);
|
|
4017
|
-
if (
|
|
4049
|
+
if (isSoftFixDispatch(item)) {
|
|
4050
|
+
const originalAgent = item.agent;
|
|
4051
|
+
const altAgent = resolvePendingDispatchAgent(item, config);
|
|
4052
|
+
if (altAgent && altAgent !== originalAgent && !busyAgents.has(altAgent)) {
|
|
4053
|
+
log('info', `Reassigning ${item.id} from ${originalAgent} to ${altAgent} — soft fix suggestion unavailable`);
|
|
4054
|
+
assignPendingDispatchAgent(item, altAgent, config);
|
|
4055
|
+
persistPendingDispatchAgent(item);
|
|
4056
|
+
// Fall through to branch mutex / concurrency checks below.
|
|
4057
|
+
} else {
|
|
4058
|
+
log('info', `Clearing busy soft fix agent on ${item.id} (${originalAgent}) — waiting for any available agent`);
|
|
4059
|
+
clearPendingDispatchAgent(item);
|
|
4060
|
+
persistPendingDispatchAgent(item);
|
|
4061
|
+
continue;
|
|
4062
|
+
}
|
|
4063
|
+
} else if (!isHardPinned && reassignMs > 0 && item._agentBusySince) {
|
|
4018
4064
|
const busySinceMs = new Date(item._agentBusySince).getTime();
|
|
4019
4065
|
if (Date.now() - busySinceMs > reassignMs) {
|
|
4020
4066
|
const originalAgent = item.agent;
|
|
4021
|
-
const altAgent =
|
|
4067
|
+
const altAgent = resolvePendingDispatchAgent(item, config);
|
|
4022
4068
|
if (altAgent && altAgent !== originalAgent && !busyAgents.has(altAgent)) {
|
|
4023
4069
|
log('info', `Reassigning ${item.id} from ${originalAgent} to ${altAgent} — agent busy > ${reassignMs}ms`);
|
|
4024
|
-
item
|
|
4025
|
-
item.agentName = config.agents[altAgent]?.name || tempAgents.get(altAgent)?.name || altAgent;
|
|
4026
|
-
item.agentRole = config.agents[altAgent]?.role || tempAgents.get(altAgent)?.role || 'Agent';
|
|
4027
|
-
delete item._agentBusySince;
|
|
4028
|
-
delete item.skipReason;
|
|
4070
|
+
assignPendingDispatchAgent(item, altAgent, config);
|
|
4029
4071
|
// Persist reassignment to dispatch.json
|
|
4030
|
-
|
|
4031
|
-
const p = (dp.pending || []).find(d => d.id === item.id);
|
|
4032
|
-
if (p) {
|
|
4033
|
-
p.agent = altAgent;
|
|
4034
|
-
p.agentName = item.agentName;
|
|
4035
|
-
p.agentRole = item.agentRole;
|
|
4036
|
-
delete p._agentBusySince;
|
|
4037
|
-
delete p.skipReason;
|
|
4038
|
-
}
|
|
4039
|
-
return dp;
|
|
4040
|
-
});
|
|
4072
|
+
persistPendingDispatchAgent(item);
|
|
4041
4073
|
// Fall through to branch mutex / concurrency checks below
|
|
4042
4074
|
} else {
|
|
4043
4075
|
continue; // No alternative agent available — keep waiting
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1666",
|
|
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"
|