@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 CHANGED
@@ -1,15 +1,15 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1665 (2026-05-01)
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
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-01T19:30:38.180Z"
4
+ "cachedAt": "2026-05-01T20:53:59.616Z"
5
5
  }
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 = resolveAgent(routing.normalizeWorkType(item.type, WORK_TYPE.IMPLEMENT), config, { agentHints: routing.extractAgentHints(item.meta?.item) });
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.agent = fallback;
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
- mutateDispatch((dp) => {
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 = resolveAgent(routing.normalizeWorkType(item.type, WORK_TYPE.IMPLEMENT), config);
4035
+ const altAgent = resolvePendingDispatchAgent(item, config);
3990
4036
  if (altAgent && altAgent !== item.agent) {
3991
4037
  const prevAgent = item.agent;
3992
- item.agent = altAgent;
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
- mutateDispatch((dp) => {
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 (!isHardPinned && reassignMs > 0 && item._agentBusySince) {
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 = resolveAgent(routing.normalizeWorkType(item.type, WORK_TYPE.IMPLEMENT), config, { agentHints: routing.extractAgentHints(item.meta?.item) });
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.agent = altAgent;
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
- mutateDispatch((dp) => {
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.1665",
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"