@yemi33/minions 0.1.1611 → 0.1.1613

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,8 +1,9 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1611 (2026-04-28)
3
+ ## 0.1.1613 (2026-04-28)
4
4
 
5
5
  ### Features
6
+ - fix npm test failures (#1846)
6
7
  - hard-pin agent assignment when CC names a specific agent
7
8
  - auto-detect available CLI runtimes and pin engine.defaultCli
8
9
  - match runtime tags to actual logos (pixel-crab Claude, mascot Copilot)
@@ -183,8 +183,8 @@ function copyLlmText(btn) {
183
183
  const clone = container.cloneNode(true);
184
184
  clone.querySelectorAll('.llm-copy-btn').forEach(b => b.remove());
185
185
  navigator.clipboard.writeText(clone.textContent.trim());
186
- btn.innerHTML = '✓';
187
- setTimeout(() => { btn.innerHTML = '⎘'; }, 1500);
186
+ btn.textContent = '\u2713';
187
+ setTimeout(() => { btn.textContent = '\u2398'; }, 1500);
188
188
  }
189
189
 
190
190
  /**
package/dashboard.js CHANGED
@@ -5201,6 +5201,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5201
5201
  engine: { state: engine.state, pid: engine.pid },
5202
5202
  agents: agents.map(a => ({ id: a.id, name: a.name, status: a.status })),
5203
5203
  projects: PROJECTS.map(p => ({ name: p.name, reachable: fs.existsSync(p.localPath) })),
5204
+ minionsDir: MINIONS_DIR,
5204
5205
  uptime: process.uptime(),
5205
5206
  timestamp: new Date().toISOString()
5206
5207
  };
package/engine/queries.js CHANGED
@@ -11,7 +11,7 @@ const shared = require('./shared');
11
11
 
12
12
  const { safeRead, safeReadDir, safeJson, safeWrite, getProjects, mutateJsonFileLocked,
13
13
  projectWorkItemsPath, projectPrPath, parseSkillFrontmatter, KB_CATEGORIES,
14
- WI_STATUS, DONE_STATUSES, PRD_ITEM_STATUS, PR_STATUS, ENGINE_DEFAULTS } = shared;
14
+ WI_STATUS, DONE_STATUSES, PRD_ITEM_STATUS, PR_STATUS, ENGINE_DEFAULTS, DEFAULT_AGENT_METRICS } = shared;
15
15
 
16
16
  /**
17
17
  * Read the first `bytes` and last `bytes` of a file efficiently using byte offsets.
@@ -196,6 +196,14 @@ function getEngineLog() {
196
196
  function getMetrics() {
197
197
  const metrics = safeJson(path.join(ENGINE_DIR, 'metrics.json')) || {};
198
198
 
199
+ for (const [agentId, m] of Object.entries(metrics)) {
200
+ if (agentId.startsWith('_')) continue;
201
+ metrics[agentId] = {
202
+ ...DEFAULT_AGENT_METRICS,
203
+ ...(m && typeof m === 'object' && !Array.isArray(m) ? m : {}),
204
+ };
205
+ }
206
+
199
207
  // Enrich agent PR counts from pull-requests.json (source of truth)
200
208
  const allPrs = getPullRequests();
201
209
  const prCountByAgent = {};
package/engine/routing.js CHANGED
@@ -116,7 +116,15 @@ function setTempBudget(n) {
116
116
  }
117
117
  function getTempBudget() { return _tempBudget; }
118
118
 
119
- function resolveAgent(workType, config, authorAgent = null) {
119
+ function normalizeAgentHints(agentHints, authorAgent = null) {
120
+ const raw = Array.isArray(agentHints) ? agentHints : (agentHints ? [agentHints] : []);
121
+ return raw
122
+ .map(id => id === '_author_' ? authorAgent : id)
123
+ .map(id => typeof id === 'string' ? id.trim().toLowerCase() : '')
124
+ .filter(Boolean);
125
+ }
126
+
127
+ function resolveAgent(workType, config, authorAgent = null, agentHints = null) {
120
128
  const routes = getRoutingTableCached();
121
129
  const route = routes[workType] || routes['implement'];
122
130
  const agents = config.agents || {};
@@ -145,6 +153,14 @@ function resolveAgent(workType, config, authorAgent = null) {
145
153
  return null;
146
154
  };
147
155
 
156
+ const hintedAgents = normalizeAgentHints(agentHints, authorAgent);
157
+ if (hintedAgents.length > 0) {
158
+ for (const id of hintedAgents) {
159
+ if (isAvailable(id)) { _claimedAgents.add(id); return id; }
160
+ }
161
+ return null;
162
+ }
163
+
148
164
  // Resolve _any_ token — pick any available agent (#480)
149
165
  if (preferred === '_any_') { const pick = pickAnyIdle(); if (pick) return pick; }
150
166
  else if (preferred && isAvailable(preferred)) { _claimedAgents.add(preferred); return preferred; }
@@ -192,4 +208,5 @@ module.exports = {
192
208
  resolveAgent,
193
209
  setTempBudget,
194
210
  getTempBudget,
211
+ normalizeAgentHints,
195
212
  };
package/engine.js CHANGED
@@ -2441,7 +2441,8 @@ function discoverFromWorkItems(config, project) {
2441
2441
  item._decomposing = true;
2442
2442
  needsWrite = true;
2443
2443
  }
2444
- const agentId = item.agent || resolveAgent(workType, config);
2444
+ const agentHints = item.preferred_agent || item.agents || null;
2445
+ const agentId = item.agent || resolveAgent(workType, config, null, agentHints);
2445
2446
  if (!agentId) {
2446
2447
  // Check if reason is budget
2447
2448
  const cfgAgents = config.agents || {};
@@ -2960,7 +2961,8 @@ function discoverCentralWorkItems(config) {
2960
2961
 
2961
2962
  } else {
2962
2963
  // ─── Normal: single agent dispatch ──────────────────────────────
2963
- const agentId = item.agent || resolveAgent(workType, config);
2964
+ const agentHints = item.preferred_agent || item.agents || null;
2965
+ const agentId = item.agent || resolveAgent(workType, config, null, agentHints);
2964
2966
  if (!agentId) continue;
2965
2967
 
2966
2968
  const agentName = config.agents[agentId]?.name || agentId;
@@ -3602,7 +3604,8 @@ async function tickInner() {
3602
3604
  // be of type string. Received undefined` and re-queues — every tick. Try to
3603
3605
  // resolve a fallback via routing; if none is available, skip this tick.
3604
3606
  if (!item.agent || typeof item.agent !== 'string') {
3605
- const fallback = resolveAgent(item.type || WORK_TYPE.FIX, config);
3607
+ const agentHints = item.meta?.item?.preferred_agent || item.meta?.item?.agents || null;
3608
+ const fallback = resolveAgent(item.type || WORK_TYPE.FIX, config, null, agentHints);
3606
3609
  if (!fallback) {
3607
3610
  log('warn', `Pending dispatch ${item.id} has no agent and routing returned no fallback — skipping`);
3608
3611
  continue;
@@ -3660,7 +3663,7 @@ async function tickInner() {
3660
3663
  // Agent busy reassignment: if item has been waiting on a busy agent past the threshold,
3661
3664
  // try to find an alternative agent via routing. Skip explicitly assigned items.
3662
3665
  const reassignMs = config.engine?.agentBusyReassignMs ?? ENGINE_DEFAULTS.agentBusyReassignMs;
3663
- const isExplicitReassign = !!item.meta?.item?.agent;
3666
+ const isExplicitReassign = !!(item.meta?.item?.agent || item.meta?.item?.preferred_agent || item.meta?.item?.agents?.length);
3664
3667
  if (!isExplicitReassign && reassignMs > 0 && item._agentBusySince) {
3665
3668
  const busySinceMs = new Date(item._agentBusySince).getTime();
3666
3669
  if (Date.now() - busySinceMs > reassignMs) {
package/minions.js CHANGED
@@ -465,6 +465,7 @@ async function initMinions({ skipScan = false, scanRoot, scanDepth } = {}) {
465
465
  // Merge defaults — fills in new fields from upgrades while preserving user customizations
466
466
  if (!config.engine) config.engine = {};
467
467
  for (const [k, v] of Object.entries(ENGINE_DEFAULTS)) {
468
+ if (k === 'defaultCli') continue;
468
469
  if (config.engine[k] === undefined) config.engine[k] = v;
469
470
  }
470
471
  if (!config.claude) config.claude = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1611",
3
+ "version": "0.1.1613",
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"