@yemi33/minions 0.1.1003 → 0.1.1005

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/engine.js CHANGED
@@ -24,7 +24,7 @@
24
24
  const fs = require('fs');
25
25
  const path = require('path');
26
26
  const shared = require('./engine/shared');
27
- const { exec, execAsync, execSilent, runFile, ts, ENGINE_DEFAULTS: DEFAULTS,
27
+ const { exec, execAsync, execSilent, runFile, ts, ENGINE_DEFAULTS,
28
28
  WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, DISPATCH_RESULT, AGENT_STATUS,
29
29
  FAILURE_CLASS } = shared;
30
30
  const queries = require('./engine/queries');
@@ -241,8 +241,8 @@ function _maxTurnsForType(type, engineConfig) {
241
241
  // Priority: per-type config override → global config override → built-in per-type default → global default
242
242
  const perType = engineConfig.maxTurnsByType || {};
243
243
  if (perType[type]) return perType[type];
244
- const globalOverride = engineConfig.maxTurns && engineConfig.maxTurns !== DEFAULTS.maxTurns ? engineConfig.maxTurns : null;
245
- return globalOverride || _MAX_TURNS_BY_TYPE[type] || DEFAULTS.maxTurns;
244
+ const globalOverride = engineConfig.maxTurns && engineConfig.maxTurns !== ENGINE_DEFAULTS.maxTurns ? engineConfig.maxTurns : null;
245
+ return globalOverride || _MAX_TURNS_BY_TYPE[type] || ENGINE_DEFAULTS.maxTurns;
246
246
  }
247
247
 
248
248
  // Resolve dependency plan item IDs to their PR branches
@@ -373,8 +373,8 @@ async function spawnAgent(dispatchItem, config) {
373
373
  let cwd = rootDir;
374
374
  let worktreePath = null;
375
375
  let branchName = meta?.branch ? sanitizeBranch(meta.branch) : null;
376
- const worktreeCreateTimeout = Math.max(60000, Number(engineConfig.worktreeCreateTimeout) || DEFAULTS.worktreeCreateTimeout);
377
- const worktreeCreateRetries = Math.max(0, Math.min(3, Number(engineConfig.worktreeCreateRetries) || DEFAULTS.worktreeCreateRetries));
376
+ const worktreeCreateTimeout = Math.max(60000, Number(engineConfig.worktreeCreateTimeout) || ENGINE_DEFAULTS.worktreeCreateTimeout);
377
+ const worktreeCreateRetries = Math.max(0, Math.min(3, Number(engineConfig.worktreeCreateRetries) || ENGINE_DEFAULTS.worktreeCreateRetries));
378
378
  const _gitOpts = { stdio: 'pipe', timeout: 30000, windowsHide: true, env: shared.gitEnv() };
379
379
  const _worktreeGitOpts = { ..._gitOpts, timeout: worktreeCreateTimeout };
380
380
 
@@ -1021,7 +1021,7 @@ async function spawnAgent(dispatchItem, config) {
1021
1021
  // Build resume args
1022
1022
  const resumeArgs = [
1023
1023
  '--output-format', claudeConfig?.outputFormat || 'stream-json',
1024
- '--max-turns', String(engineConfig?.maxTurns || DEFAULTS.maxTurns),
1024
+ '--max-turns', String(engineConfig?.maxTurns || ENGINE_DEFAULTS.maxTurns),
1025
1025
  '--verbose',
1026
1026
  '--permission-mode', claudeConfig?.permissionMode || 'bypassPermissions',
1027
1027
  '--resume', steerSessionId,
@@ -1427,7 +1427,7 @@ function reconcileItemsWithPrs(items, allPrs, { onlyIds } = {}) {
1427
1427
 
1428
1428
  let exactPr = allPrs.find(pr => (pr.prdItems || []).includes(wi.id));
1429
1429
  if (!exactPr) {
1430
- const linkedPrId = Object.keys(prLinks).find(prId => prLinks[prId] === wi.id);
1430
+ const linkedPrId = Object.keys(prLinks).find(prId => (prLinks[prId] || []).includes(wi.id));
1431
1431
  if (linkedPrId) exactPr = allPrs.find(pr => pr.id === linkedPrId) || { id: linkedPrId };
1432
1432
  }
1433
1433
  // Branch-based matching: PR branch contains the work item ID (e.g. work/P-k7m2v9a1)
@@ -1878,7 +1878,7 @@ function clearPendingHumanFeedbackFlag(projectMeta, prId) {
1878
1878
  try {
1879
1879
  const prsPath = projectPrPath(projectMeta);
1880
1880
  mutatePullRequests(prsPath, prs => {
1881
- const target = prs.find(p => p.id === prId);
1881
+ const target = shared.findPrRecord(prs, prId, projectMeta);
1882
1882
  if (!target?.humanFeedback?.pendingFix) return;
1883
1883
  target.humanFeedback.pendingFix = false;
1884
1884
  });
@@ -1892,28 +1892,38 @@ async function discoverFromPrs(config, project) {
1892
1892
  const src = project?.workSources?.pullRequests || config.workSources?.pullRequests;
1893
1893
  if (!src?.enabled) return [];
1894
1894
 
1895
- const prs = safeJson(projectPrPath(project)) || [];
1895
+ const prs = queries.getPrs(project);
1896
1896
  const cooldownMs = (src.cooldownMinutes || 30) * 60 * 1000;
1897
1897
  const newWork = [];
1898
1898
 
1899
1899
  const projMeta = { name: project?.name, localPath: project?.localPath };
1900
+ const projectsByName = new Map(shared.getProjects(config).map(p => [p.name, p]));
1900
1901
 
1901
1902
  // Resolve poll-enabled per project — stale reviewStatus is untrustworthy without poller
1902
1903
  const isAdoProject = project?.repoHost !== 'github';
1903
1904
  const pollEnabled = isAdoProject
1904
- ? (config.engine?.adoPollEnabled ?? DEFAULTS.adoPollEnabled)
1905
- : (config.engine?.ghPollEnabled ?? DEFAULTS.ghPollEnabled);
1905
+ ? (config.engine?.adoPollEnabled ?? ENGINE_DEFAULTS.adoPollEnabled)
1906
+ : (config.engine?.ghPollEnabled ?? ENGINE_DEFAULTS.ghPollEnabled);
1906
1907
  const evalLoopEnabled = config.engine?.evalLoop !== false;
1907
1908
 
1908
1909
  // Collect active PR dispatches to prevent simultaneous review+fix on same PR
1909
1910
  const dispatch = getDispatch();
1910
1911
  const activePrIds = new Set(
1911
- (dispatch.active || []).filter(d => d.meta?.pr?.id).map(d => d.meta.pr.id)
1912
+ (dispatch.active || [])
1913
+ .filter(d => d.meta?.pr?.id)
1914
+ .map(d => {
1915
+ const dispatchProject = d.meta?.project?.name
1916
+ ? (projectsByName.get(d.meta.project.name) || d.meta.project)
1917
+ : (d.meta?.project || null);
1918
+ return shared.getCanonicalPrId(dispatchProject, d.meta.pr, d.meta.pr?.url || '');
1919
+ })
1920
+ .filter(Boolean)
1912
1921
  );
1913
1922
 
1914
1923
  const knownAgents = new Set(Object.keys(config.agents || {}));
1915
1924
  for (const pr of prs) {
1916
1925
  if (pr.status !== PR_STATUS.ACTIVE || pr._contextOnly) continue;
1926
+ const prDisplayId = shared.getPrDisplayId(pr);
1917
1927
  if (activePrIds.has(pr.id)) continue; // Skip PRs with active dispatch (prevent race)
1918
1928
  // Branch mutex: skip if PR branch is locked by any active dispatch (cross-type collision)
1919
1929
  if (pr.branch && isBranchActive(pr.branch)) {
@@ -1925,7 +1935,7 @@ async function discoverFromPrs(config, project) {
1925
1935
  const isAgentPr = knownAgents.has((pr.agent || '').toLowerCase()) || (pr.prdItems && pr.prdItems.length > 0) || pr._autoObserve;
1926
1936
  if (!isAgentPr) continue;
1927
1937
 
1928
- const prNumber = (pr.id || '').replace(/^PR-/, '');
1938
+ const prNumber = shared.getPrNumber(pr);
1929
1939
  // Use reviewStatus as single source of truth (synced from ADO/GitHub votes)
1930
1940
  // minionsReview tracks metadata (reviewer, note) but not the authoritative status
1931
1941
  const reviewStatus = pr.reviewStatus || 'pending';
@@ -1935,17 +1945,17 @@ async function discoverFromPrs(config, project) {
1935
1945
  const awaitingReReview = reviewStatus === 'waiting' && !!pr.minionsReview?.fixedAt;
1936
1946
 
1937
1947
  // Review→fix cycle cap — stop review/fix dispatch after N iterations, but allow build fixes and conflict fixes
1938
- const evalMax = config.engine?.evalMaxIterations ?? DEFAULTS.evalMaxIterations;
1948
+ const evalMax = config.engine?.evalMaxIterations ?? ENGINE_DEFAULTS.evalMaxIterations;
1939
1949
  const evalCycles = pr._reviewFixCycles || 0;
1940
1950
  const evalEscalated = evalCycles >= evalMax;
1941
1951
  if (evalEscalated && !pr._evalEscalated) {
1942
- writeInboxAlert(`eval-escalated-${pr.agent || 'unassigned'}-${pr.id}`,
1952
+ writeInboxAlert(`eval-escalated-${pr.agent || 'unassigned'}-${prDisplayId}`,
1943
1953
  `# Review Loop Escalation\n\n**PR ${pr.id}**: ${pr.title || ''} on branch \`${pr.branch || 'unknown'}\` has gone through **${evalCycles}** review→fix cycles without approval.\n\n` +
1944
1954
  `Last review: ${pr.minionsReview?.note ? pr.minionsReview.note.slice(0, 200) : 'See PR thread'}\n\n` +
1945
1955
  `Auto-dispatch of reviews and fixes has been suspended. Please review the PR manually.`);
1946
1956
  try {
1947
1957
  mutatePullRequests(projectPrPath(project), prs => {
1948
- const target = prs.find(p => p.id === pr.id);
1958
+ const target = shared.findPrRecord(prs, pr, project);
1949
1959
  if (target) target._evalEscalated = true;
1950
1960
  });
1951
1961
  } catch (e) { log('warn', 'mark eval escalated: ' + e.message); }
@@ -1957,7 +1967,7 @@ async function discoverFromPrs(config, project) {
1957
1967
  const alreadyReviewed = pr.lastReviewedAt && (!pr.lastPushedAt || pr.lastPushedAt <= pr.lastReviewedAt);
1958
1968
  const needsReview = reviewEnabled && reviewStatus === 'pending' && !alreadyReviewed && !evalEscalated;
1959
1969
  if (needsReview) {
1960
- const key = `review-${project?.name || 'default'}-${pr.id}`;
1970
+ const key = `review-${project?.name || 'default'}-${prDisplayId}`;
1961
1971
  if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
1962
1972
 
1963
1973
  // Pre-dispatch live vote check — cached reviewStatus may be stale (poll lag ~6 min)
@@ -1972,7 +1982,7 @@ async function discoverFromPrs(config, project) {
1972
1982
  try {
1973
1983
  mutateJsonFileLocked(projectPrPath(project), data => {
1974
1984
  if (!Array.isArray(data)) return data;
1975
- const target = data.find(p => p.id === pr.id);
1985
+ const target = shared.findPrRecord(data, pr, project);
1976
1986
  if (target && target.reviewStatus !== 'approved') target.reviewStatus = liveStatus;
1977
1987
  return data;
1978
1988
  });
@@ -1998,7 +2008,7 @@ async function discoverFromPrs(config, project) {
1998
2008
  const needsReReview = reviewEnabled && reviewStatus === 'waiting' &&
1999
2009
  fixedAfterReview && !evalEscalated;
2000
2010
  if (needsReReview) {
2001
- const key = `rereview-${project?.name || 'default'}-${pr.id}`;
2011
+ const key = `rereview-${project?.name || 'default'}-${prDisplayId}`;
2002
2012
  // Skip isAlreadyDispatched — fixedAfterReview/lastReviewedAt already dedupe; the 1hr
2003
2013
  // completed-dispatch window would block legitimate re-reviews within the hour after a fix
2004
2014
  if (isOnCooldown(key, cooldownMs)) continue;
@@ -2013,7 +2023,7 @@ async function discoverFromPrs(config, project) {
2013
2023
  try {
2014
2024
  mutateJsonFileLocked(projectPrPath(project), data => {
2015
2025
  if (!Array.isArray(data)) return data;
2016
- const target = data.find(p => p.id === pr.id);
2026
+ const target = shared.findPrRecord(data, pr, project);
2017
2027
  if (target && target.reviewStatus !== 'approved') target.reviewStatus = liveStatus;
2018
2028
  return data;
2019
2029
  });
@@ -2036,7 +2046,7 @@ async function discoverFromPrs(config, project) {
2036
2046
  // Gate on evalLoopEnabled — the review→fix cycle is the eval loop
2037
2047
  let fixDispatched = false;
2038
2048
  if (evalLoopEnabled && reviewStatus === 'changes-requested' && !awaitingReReview && !evalEscalated) {
2039
- const key = `fix-${project?.name || 'default'}-${pr.id}`;
2049
+ const key = `fix-${project?.name || 'default'}-${prDisplayId}`;
2040
2050
  if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
2041
2051
  const agentId = resolveAgent('fix', config, pr.agent);
2042
2052
  if (!agentId) continue;
@@ -2050,7 +2060,7 @@ async function discoverFromPrs(config, project) {
2050
2060
  // Increment review→fix cycle counter
2051
2061
  try {
2052
2062
  mutatePullRequests(projectPrPath(project), prs => {
2053
- const target = prs.find(p => p.id === pr.id);
2063
+ const target = shared.findPrRecord(prs, pr, project);
2054
2064
  if (target) target._reviewFixCycles = (target._reviewFixCycles || 0) + 1;
2055
2065
  });
2056
2066
  } catch (e) { log('warn', 'increment review-fix cycles: ' + e.message); }
@@ -2058,7 +2068,7 @@ async function discoverFromPrs(config, project) {
2058
2068
  }
2059
2069
 
2060
2070
  // PRs with pending human feedback (skip if review-fix already dispatched above)
2061
- const humanFixKey = `human-fix-${project?.name || 'default'}-${pr.id}`;
2071
+ const humanFixKey = `human-fix-${project?.name || 'default'}-${prDisplayId}`;
2062
2072
  const hasCoalescedFeedback = (dispatchCooldowns.get(humanFixKey)?.pendingContexts || []).length > 0;
2063
2073
  if ((pr.humanFeedback?.pendingFix || hasCoalescedFeedback) && !awaitingReReview && !fixDispatched) {
2064
2074
  const key = humanFixKey;
@@ -2099,17 +2109,17 @@ async function discoverFromPrs(config, project) {
2099
2109
  // Grace period: after a build fix push, wait for CI to run before re-dispatching
2100
2110
  // Skip if build hasn't transitioned since last fix (still showing the old failure)
2101
2111
  if (pr._buildFixPushedAt && pr.buildStatus === 'failing') {
2102
- const gracePeriodMs = config.engine?.buildFixGracePeriod ?? DEFAULTS.buildFixGracePeriod;
2112
+ const gracePeriodMs = config.engine?.buildFixGracePeriod ?? ENGINE_DEFAULTS.buildFixGracePeriod;
2103
2113
  if (Date.now() - new Date(pr._buildFixPushedAt).getTime() < gracePeriodMs) continue;
2104
2114
  }
2105
- const autoFixBuilds = config.engine?.autoFixBuilds ?? DEFAULTS.autoFixBuilds;
2115
+ const autoFixBuilds = config.engine?.autoFixBuilds ?? ENGINE_DEFAULTS.autoFixBuilds;
2106
2116
  if (autoFixBuilds && pr.status === PR_STATUS.ACTIVE && pr.buildStatus === 'failing') {
2107
- const maxBuildFix = config.engine?.maxBuildFixAttempts ?? DEFAULTS.maxBuildFixAttempts;
2117
+ const maxBuildFix = config.engine?.maxBuildFixAttempts ?? ENGINE_DEFAULTS.maxBuildFixAttempts;
2108
2118
 
2109
2119
  // Check if max retry cap reached — escalate to human instead of dispatching another fix
2110
2120
  if ((pr.buildFixAttempts || 0) >= maxBuildFix) {
2111
2121
  if (!pr.buildFixEscalated) {
2112
- writeInboxAlert(`build-fix-escalated-${pr.agent || 'unassigned'}-${pr.id}`,
2122
+ writeInboxAlert(`build-fix-escalated-${pr.agent || 'unassigned'}-${prDisplayId}`,
2113
2123
  `# Build Fix Escalation\n\n` +
2114
2124
  `**PR ${pr.id}**: ${pr.title || ''} on branch \`${pr.branch || 'unknown'}\` has failed **${pr.buildFixAttempts}** consecutive auto-fix attempts.\n` +
2115
2125
  `**Last failure:** ${pr.buildFailReason || 'Check CI pipeline for details'}\n\n` +
@@ -2118,7 +2128,7 @@ async function discoverFromPrs(config, project) {
2118
2128
  try {
2119
2129
  const prPath = projectPrPath(project);
2120
2130
  mutatePullRequests(prPath, prs => {
2121
- const target = prs.find(p => p.id === pr.id);
2131
+ const target = shared.findPrRecord(prs, pr, project);
2122
2132
  if (target) target.buildFixEscalated = true;
2123
2133
  });
2124
2134
  } catch (e) { log('warn', 'mark build fix escalated: ' + e.message); }
@@ -2127,7 +2137,7 @@ async function discoverFromPrs(config, project) {
2127
2137
  continue;
2128
2138
  }
2129
2139
 
2130
- const key = `build-fix-${project?.name || 'default'}-${pr.id}`;
2140
+ const key = `build-fix-${project?.name || 'default'}-${prDisplayId}`;
2131
2141
  if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
2132
2142
  const agentId = resolveAgent('fix', config, pr.agent);
2133
2143
  if (!agentId) continue;
@@ -2147,7 +2157,7 @@ async function discoverFromPrs(config, project) {
2147
2157
  try {
2148
2158
  const prPath = projectPrPath(project);
2149
2159
  mutatePullRequests(prPath, prs => {
2150
- const target = prs.find(p => p.id === pr.id);
2160
+ const target = shared.findPrRecord(prs, pr, project);
2151
2161
  if (target) {
2152
2162
  target.buildFixAttempts = (target.buildFixAttempts || 0) + 1;
2153
2163
  target._buildFixPushedAt = ts();
@@ -2167,12 +2177,12 @@ async function discoverFromPrs(config, project) {
2167
2177
  alertBody += `**Error preview:**\n\`\`\`\n${logPreview}\n\`\`\`\n\n`;
2168
2178
  }
2169
2179
  alertBody += `A fix agent has been dispatched to address this. Review the fix when complete.\n`;
2170
- writeInboxAlert(`build-fail-${pr.agent}-${pr.id}`, alertBody);
2180
+ writeInboxAlert(`build-fail-${pr.agent}-${prDisplayId}`, alertBody);
2171
2181
  // Mark notified to prevent duplicate alerts
2172
2182
  try {
2173
2183
  const prPath = projectPrPath(project);
2174
2184
  mutatePullRequests(prPath, prs => {
2175
- const target = prs.find(p => p.id === pr.id);
2185
+ const target = shared.findPrRecord(prs, pr, project);
2176
2186
  if (target) {
2177
2187
  target._buildFailNotified = true;
2178
2188
  }
@@ -2182,9 +2192,9 @@ async function discoverFromPrs(config, project) {
2182
2192
  }
2183
2193
 
2184
2194
  // PRs with merge conflicts — dispatch fix to resolve (gated by autoFixConflicts)
2185
- const autoFixConflicts = config.engine?.autoFixConflicts ?? DEFAULTS.autoFixConflicts;
2195
+ const autoFixConflicts = config.engine?.autoFixConflicts ?? ENGINE_DEFAULTS.autoFixConflicts;
2186
2196
  if (autoFixConflicts && pr.status === PR_STATUS.ACTIVE && pr._mergeConflict && !fixDispatched) {
2187
- const key = `conflict-fix-${project?.name || 'default'}-${pr.id}`;
2197
+ const key = `conflict-fix-${project?.name || 'default'}-${prDisplayId}`;
2188
2198
  // Suppress re-dispatch for 10 min after last attempt — ADO/GitHub recomputes
2189
2199
  // mergeStatus asynchronously (1–5 min lag), so the flag may stay set even after
2190
2200
  // a successful push. _conflictFixedAt is cleared when the poller confirms clean status.
@@ -2203,7 +2213,7 @@ async function discoverFromPrs(config, project) {
2203
2213
  // Record dispatch timestamp so re-dispatch is suppressed during ADO lag window
2204
2214
  try {
2205
2215
  mutatePullRequests(projectPrPath(project), prs => {
2206
- const target = prs.find(p => p.id === pr.id);
2216
+ const target = shared.findPrRecord(prs, pr, project);
2207
2217
  if (target) target._conflictFixedAt = new Date().toISOString();
2208
2218
  });
2209
2219
  } catch (e) { log('warn', `conflict-fix timestamp: ${e.message}`); }
@@ -2808,7 +2818,7 @@ function discoverCentralWorkItems(config) {
2808
2818
  meta: {
2809
2819
  dispatchKey: fanKey, source: 'central-work-item-fanout', item, parentKey: key,
2810
2820
  branch: fanBranch,
2811
- deadline: item.timeout ? Date.now() + item.timeout : Date.now() + (config.engine?.fanOutTimeout || config.engine?.agentTimeout || DEFAULTS.agentTimeout)
2821
+ deadline: item.timeout ? Date.now() + item.timeout : Date.now() + (config.engine?.fanOutTimeout || config.engine?.agentTimeout || ENGINE_DEFAULTS.agentTimeout)
2812
2822
  }
2813
2823
  });
2814
2824
  }
@@ -3088,7 +3098,7 @@ async function discoverWork(config) {
3088
3098
  );
3089
3099
  if (hasIncompleteImplements) {
3090
3100
  const activeCount = (getDispatch().active || []).length;
3091
- const maxConcurrent = config.engine?.maxConcurrent ?? DEFAULTS.maxConcurrent;
3101
+ const maxConcurrent = config.engine?.maxConcurrent ?? ENGINE_DEFAULTS.maxConcurrent;
3092
3102
  const freeSlots = Math.max(0, maxConcurrent - activeCount);
3093
3103
  if (freeSlots === 0) {
3094
3104
  if (allReviews.length > 0) {
@@ -3210,10 +3220,10 @@ async function tickInner() {
3210
3220
  });
3211
3221
  }
3212
3222
 
3213
- const adoPollEnabled = config.engine?.adoPollEnabled ?? DEFAULTS.adoPollEnabled;
3214
- const ghPollEnabled = config.engine?.ghPollEnabled ?? DEFAULTS.ghPollEnabled;
3215
- const adoPollStatusEvery = Math.max(1, Number(config.engine?.adoPollStatusEvery) || DEFAULTS.adoPollStatusEvery);
3216
- const adoPollCommentsEvery = Math.max(1, Number(config.engine?.adoPollCommentsEvery) || DEFAULTS.adoPollCommentsEvery);
3223
+ const adoPollEnabled = config.engine?.adoPollEnabled ?? ENGINE_DEFAULTS.adoPollEnabled;
3224
+ const ghPollEnabled = config.engine?.ghPollEnabled ?? ENGINE_DEFAULTS.ghPollEnabled;
3225
+ const adoPollStatusEvery = Math.max(1, Number(config.engine?.adoPollStatusEvery) || ENGINE_DEFAULTS.adoPollStatusEvery);
3226
+ const adoPollCommentsEvery = Math.max(1, Number(config.engine?.adoPollCommentsEvery) || ENGINE_DEFAULTS.adoPollCommentsEvery);
3217
3227
 
3218
3228
  // 2.6. Poll PR status: build, review, merge (every adoPollStatusEvery ticks, default ~6 minutes)
3219
3229
  // Awaited so PR state is consistent before discoverWork reads it
@@ -3416,7 +3426,7 @@ async function tickInner() {
3416
3426
  if (busyAgents.has(item.agent)) {
3417
3427
  // Agent busy reassignment: if item has been waiting on a busy agent past the threshold,
3418
3428
  // try to find an alternative agent via routing. Skip explicitly assigned items.
3419
- const reassignMs = config.engine?.agentBusyReassignMs ?? DEFAULTS.agentBusyReassignMs;
3429
+ const reassignMs = config.engine?.agentBusyReassignMs ?? ENGINE_DEFAULTS.agentBusyReassignMs;
3420
3430
  const isExplicitReassign = !!item.meta?.item?.agent;
3421
3431
  if (!isExplicitReassign && reassignMs > 0 && item._agentBusySince) {
3422
3432
  const busySinceMs = new Date(item._agentBusySince).getTime();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1003",
3
+ "version": "0.1.1005",
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"