@yemi33/minions 0.1.2009 → 0.1.2011

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.
@@ -249,8 +249,9 @@ async function openSettings() {
249
249
  '<div style="display:flex;flex-direction:column;gap:6px;margin-top:8px">' +
250
250
  settingsToggle('Claude bare mode', 'set-claudeBareMode', !!e.claudeBareMode, '--bare suppresses CLAUDE.md auto-discovery; pair with explicit ccSystemPrompt or context will be lost') +
251
251
  '</div>' +
252
- '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px">' +
253
- settingsField('Claude fallback model', 'set-claudeFallbackModel', e.claudeFallbackModel || '', '', 'Used by --fallback-model on rate-limit / overload (Claude only)') +
252
+ '<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-top:8px">' +
253
+ settingsField('Claude fallback model', 'set-claudeFallbackModel', e.claudeFallbackModel || '', 'e.g. sonnet', 'Passed via Claude --fallback-model. The Claude CLI swaps to this only on rate-limit (429). Used together with engine.copilotFallbackModel for the engine-level MODEL_UNAVAILABLE (overloaded/503) retry — see CLAUDE.md.') +
254
+ settingsField('Copilot fallback model', 'set-copilotFallbackModel', e.copilotFallbackModel || '', 'e.g. gpt-5.4', 'Copilot has no --fallback-model flag. On a MODEL_UNAVAILABLE (overloaded/503) retry, the engine OVERRIDES --model with this value (Copilot only).') +
254
255
  settingsField('Max budget (USD)', 'set-maxBudgetUsd', e.maxBudgetUsd != null ? String(e.maxBudgetUsd) : '', '', 'Fleet ceiling for --max-budget-usd. 0 is a valid cap (read-only / dry-run). Empty = no cap. Claude only.') +
255
256
  '</div>' +
256
257
  '<div style="display:flex;flex-direction:column;gap:6px;margin-top:8px">' +
@@ -627,6 +628,7 @@ async function saveSettings() {
627
628
  ccEffort: document.getElementById('set-ccEffort').value || null,
628
629
  claudeBareMode: !!document.getElementById('set-claudeBareMode')?.checked,
629
630
  claudeFallbackModel: (document.getElementById('set-claudeFallbackModel')?.value ?? '').trim(),
631
+ copilotFallbackModel: (document.getElementById('set-copilotFallbackModel')?.value ?? '').trim(),
630
632
  copilotDisableBuiltinMcps: !!document.getElementById('set-copilotDisableBuiltinMcps')?.checked,
631
633
  copilotSuppressAgentsMd: !!document.getElementById('set-copilotSuppressAgentsMd')?.checked,
632
634
  copilotStreamMode: document.getElementById('set-copilotStreamMode')?.value || 'on',
package/dashboard.js CHANGED
@@ -8308,6 +8308,17 @@ What would you like to discuss or change? When you're happy, say "approve" and I
8308
8308
  if (_isClear(e.claudeFallbackModel)) _deleteEngineConfig('claudeFallbackModel');
8309
8309
  else _setEngineConfig('claudeFallbackModel', String(e.claudeFallbackModel));
8310
8310
  }
8311
+ // W-mpg6isvy000xca4d — Copilot fallback model. Mirrors the
8312
+ // claudeFallbackModel handler above. Empty / null clears; any string
8313
+ // value is stored verbatim. The dispatch retry path (engine.js
8314
+ // spawnAgent) reads engine.copilotFallbackModel when the previous
8315
+ // failure was FAILURE_CLASS.MODEL_UNAVAILABLE and the runtime has
8316
+ // capabilities.fallbackModel === false (Copilot has no --fallback-model
8317
+ // flag, so we override --model directly).
8318
+ if (e.copilotFallbackModel !== undefined) {
8319
+ if (_isClear(e.copilotFallbackModel)) _deleteEngineConfig('copilotFallbackModel');
8320
+ else _setEngineConfig('copilotFallbackModel', String(e.copilotFallbackModel));
8321
+ }
8311
8322
  if (e.copilotStreamMode !== undefined) {
8312
8323
  const valid = ['on', 'off'];
8313
8324
  if (_isClear(e.copilotStreamMode)) _deleteEngineConfig('copilotStreamMode');
@@ -654,6 +654,12 @@ function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', res
654
654
  wi.status = WI_STATUS.PENDING;
655
655
  wi._lastRetryReason = reason || '';
656
656
  wi._lastRetryAt = ts();
657
+ // W-mpg6isvy000xca4d — surface the previous failure class so the
658
+ // next spawn can swap in a runtime-appropriate fallback model
659
+ // when the previous attempt was bounced for MODEL_UNAVAILABLE
660
+ // (overloaded_error / 503). Empty string clears any stale
661
+ // value from an earlier failure cycle.
662
+ wi._lastFailureClass = failureClass || '';
657
663
  delete wi.failReason;
658
664
  delete wi.failedAt;
659
665
  delete wi.dispatched_at;
@@ -683,6 +689,7 @@ function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', res
683
689
  [FAILURE_CLASS.INVALID_MANAGED_SPAWN]: 'managed-spawn.json failed validation (bad schema, workdir, or allowlist — see inbox alert)',
684
690
  [FAILURE_CLASS.MANAGED_SPAWN_HEALTHCHECK_FAILED]: 'managed-spawn spec(s) failed healthcheck within timeout (failing PIDs killed; surviving siblings stay alive)',
685
691
  [FAILURE_CLASS.INJECTION_FLAGGED]: 'agent flagged a prompt-injection attempt in spliced untrusted content — human review of the listed sources required before re-dispatch',
692
+ [FAILURE_CLASS.MODEL_UNAVAILABLE]: 'requested model returned overloaded/503 — fallback model swapped in for retry',
686
693
  [FAILURE_CLASS.UNKNOWN]: 'unknown error',
687
694
  };
688
695
  const classLabel = failureClass ? (CLASS_LABELS[failureClass] || failureClass) : '';
@@ -1963,8 +1963,19 @@ function recordPrNoOpFixAttempt(target, cause, source, dispatchItem, branchChang
1963
1963
  ).slice(0, 500);
1964
1964
  target._lastDispatchByCause = target._lastDispatchByCause
1965
1965
  && typeof target._lastDispatchByCause === 'object' ? target._lastDispatchByCause : {};
1966
+ // W-mpg6wptq0011cc68: distinguish indeterminate noops (detection could not
1967
+ // prove the branch advanced — fetch failed, worktree gone, missing baseline)
1968
+ // from confirmed noops (remote head was verified to equal beforeHead). The
1969
+ // same-head guard in engine.js MUST NOT permanently suppress on indeterminate
1970
+ // records, otherwise a single failed detection locks out future fix
1971
+ // dispatches on that head+comment until the SHA moves — and the SHA cannot
1972
+ // move unless the suppressed fix runs. The `_noOpFixes[cause].count` +
1973
+ // `prNoOpFixPauseAttempts` (default 3) safety valve still applies; only the
1974
+ // same-head guard is relaxed.
1975
+ const indeterminate = !!branchChange && branchChange.changed === null;
1966
1976
  target._lastDispatchByCause[cause] = {
1967
1977
  outcome: 'noop',
1978
+ indeterminate,
1968
1979
  headSha,
1969
1980
  reason: reasonText,
1970
1981
  dispatchedAt: now,
@@ -1032,7 +1032,17 @@ function selectPlaybook(workType, item) {
1032
1032
 
1033
1033
  function buildPrDispatch(agentId, config, project, pr, type, extraVars, taskLabel, meta) {
1034
1034
  const dispatchId = `${agentId || 'unassigned'}-${type}-${shared.uid()}`;
1035
- const vars = { ...buildBaseVars(agentId, config, project), ...extraVars, task_id: dispatchId };
1035
+ // W-mpg6wptq0011cc68: PR-context dispatches (review/fix/build-and-test) all
1036
+ // operate on the PR's branch, so default `branch_name` to `pr_branch` here
1037
+ // so shared-rules.md's `{{branch_name}}` reference resolves. Without this,
1038
+ // every PR fix/review render emits a recurring `unresolved template
1039
+ // variables: branch_name` warn. Callers may still override explicitly.
1040
+ const vars = {
1041
+ ...buildBaseVars(agentId, config, project),
1042
+ branch_name: extraVars?.pr_branch || '',
1043
+ ...extraVars,
1044
+ task_id: dispatchId,
1045
+ };
1036
1046
  const playbookName = type === 'test' ? 'build-and-test' : (type === 'review' ? 'review' : 'fix');
1037
1047
  const prompt = renderPlaybook(playbookName, vars);
1038
1048
  if (!prompt) return null;
@@ -412,6 +412,7 @@ function usesSystemPromptFile({ isResume } = {}) {
412
412
  function _runtimeFailureClass(code) {
413
413
  if (code === 'auth-failure' || code === 'budget-exceeded') return FAILURE_CLASS.PERMISSION_BLOCKED;
414
414
  if (code === 'context-limit') return FAILURE_CLASS.OUT_OF_CONTEXT;
415
+ if (code === 'model-unavailable') return FAILURE_CLASS.MODEL_UNAVAILABLE;
415
416
  if (code === 'crash') return FAILURE_CLASS.SPAWN_ERROR;
416
417
  return null;
417
418
  }
@@ -552,6 +553,16 @@ function parseError(rawOutput) {
552
553
  if (/budget.*exceed|max.budget.usd.*reach|cost.*limit.*exceed/i.test(lower)) {
553
554
  return { message: 'Claude budget cap exceeded — check your Claude account spending limit.', code: 'budget-exceeded', retriable: false };
554
555
  }
556
+ // W-mpg6isvy000xca4d — Anthropic overload / 503 / service-unavailable. Claude's
557
+ // own `--fallback-model` only fires on 429 (rate-limit); these failure modes
558
+ // hang the agent until the 5h timeout. Classify as MODEL_UNAVAILABLE so the
559
+ // dispatch loop retries with the runtime-appropriate fallback model. Match
560
+ // before the crash branch — Anthropic's overloaded responses can include
561
+ // "internal error" / "panic"-style phrasing that would otherwise be misread
562
+ // as a CLI crash.
563
+ if (/overloaded_error|service[_ ]unavailable|model.*(?:unavailable|overloaded)|\b503\b|temporarily unavailable/i.test(text)) {
564
+ return { message: 'Claude model temporarily unavailable (overloaded / 503)', code: 'model-unavailable', retriable: true };
565
+ }
555
566
  if (/internal error|panic|segmentation fault|claude.*crashed|fatal: claude/i.test(lower)) {
556
567
  return { message: 'Claude CLI crashed unexpectedly. Try again.', code: 'crash', retriable: true };
557
568
  }
@@ -602,6 +602,7 @@ function usesSystemPromptFile() {
602
602
  function _runtimeFailureClass(code) {
603
603
  if (code === 'auth-failure' || code === 'budget-exceeded') return FAILURE_CLASS.PERMISSION_BLOCKED;
604
604
  if (code === 'unknown-model') return FAILURE_CLASS.CONFIG_ERROR;
605
+ if (code === 'model-unavailable') return FAILURE_CLASS.MODEL_UNAVAILABLE;
605
606
  if (code === 'rate-limit') return FAILURE_CLASS.NETWORK_ERROR;
606
607
  if (code === 'crash') return FAILURE_CLASS.SPAWN_ERROR;
607
608
  return null;
@@ -844,6 +845,14 @@ function parseError(rawOutput) {
844
845
  if (hasExplicitAuthFailure || hasAuthStatusCode) {
845
846
  return { message: text, code: 'auth-failure', retriable: false };
846
847
  }
848
+ // W-mpg6isvy000xca4d — Copilot has no --fallback-model flag; classify
849
+ // overloaded / 503 / service_unavailable as MODEL_UNAVAILABLE so the engine
850
+ // retry can OVERRIDE --model with engine.copilotFallbackModel. Match before
851
+ // rate-limit so 503/overload never gets misread as a 429 (which would
852
+ // bucket into NETWORK_ERROR and re-spawn against the same broken model).
853
+ if (/overloaded_error|service[_ ]unavailable|model.*(?:unavailable|overloaded)|\b503\b|temporarily unavailable/i.test(text)) {
854
+ return { message: text, code: 'model-unavailable', retriable: true };
855
+ }
847
856
  if (/rate limit|too many requests|\b429\b/i.test(text)) {
848
857
  return { message: text, code: 'rate-limit', retriable: true };
849
858
  }
package/engine/shared.js CHANGED
@@ -1764,7 +1764,8 @@ const ENGINE_DEFAULTS = {
1764
1764
  ccCli: undefined, // CC/doc-chat CLI override; undefined = inherit defaultCli (independent of agent path)
1765
1765
  ccModel: undefined, // CC/doc-chat model override; undefined = inherit defaultModel
1766
1766
  claudeBareMode: false, // Claude --bare: suppress CLAUDE.md auto-discovery (per-agent override: agents.<id>.bareMode)
1767
- claudeFallbackModel: undefined,// Claude --fallback-model on rate-limit / overload (Claude-only)
1767
+ claudeFallbackModel: undefined,// Claude --fallback-model — Claude CLI honors this on rate-limit (429) only; engine retry on FAILURE_CLASS.MODEL_UNAVAILABLE keeps the flag passed so the CLI can swap internally
1768
+ copilotFallbackModel: undefined,// W-mpg6isvy000xca4d: Copilot has no --fallback-model flag; engine retry on FAILURE_CLASS.MODEL_UNAVAILABLE OVERRIDES --model directly with this value (separate knob from claudeFallbackModel because model namespaces differ across runtimes)
1768
1769
  copilotDisableBuiltinMcps: true, // Copilot --disable-builtin-mcps: keep github-mcp-server out so it can't bypass pull-requests.json tracking
1769
1770
  copilotSuppressAgentsMd: true, // Copilot --no-custom-instructions: stop AGENTS.md auto-load from fighting Minions playbook prompts
1770
1771
  copilotStreamMode: 'on', // Copilot --stream <on|off>: 'on' streams assistant.message_delta events live; 'off' batches them
@@ -2606,6 +2607,7 @@ const FAILURE_CLASS = {
2606
2607
  INVALID_MANAGED_SPAWN: 'invalid-managed-spawn', // P-7a3b1c92: agents/<id>/managed-spawn.json failed validator (bad schema, broken workdir, executable/env not on allowlist, healthcheck shape wrong). Engine refuses to spawn any spec — agent must fix file; never retryable as-is.
2607
2608
  MANAGED_SPAWN_HEALTHCHECK_FAILED: 'managed-spawn-healthcheck-failed', // P-7a3b1c92: at least one managed-spawn spec was spawned but failed its healthcheck within timeout_s. Engine killed the failing PIDs; siblings stay alive. Dispatch ERROR with the failing spec name + log tail surfaced in the inbox alert.
2608
2609
  INJECTION_FLAGGED: 'injection-flagged', // F5 (W-mpeklod3000we69c): the agent set `securityFlags.injectionAttempt:true` in its completion report after spotting a prompt-injection attempt inside an <UNTRUSTED-INPUT> fence. Engine writes a security inbox note + stamps `_securityFlag` on the WI and treats the dispatch as non-retryable so a human can review the source before the agent re-runs.
2610
+ MODEL_UNAVAILABLE: 'model-unavailable', // W-mpg6isvy000xca4d: requested model returned overloaded_error / 503 / service_unavailable. Retriable — engine swaps in the runtime-appropriate fallback model on next spawn (Claude leans on --fallback-model already plumbed; Copilot overrides --model with engine.copilotFallbackModel).
2609
2611
  UNKNOWN: 'unknown', // Unclassified failure
2610
2612
  };
2611
2613
  const ESCALATION_POLICY = {
package/engine.js CHANGED
@@ -148,6 +148,12 @@ const { renderPlaybook, validatePlaybookVars, PLAYBOOK_REQUIRED_VARS,
148
148
  buildBaseVars, buildPrDispatch, resolveTaskContext,
149
149
  getRepoHost, getRepoHostLabel, getRepoHostToolRule } = require('./engine/playbook');
150
150
 
151
+ // Per-slug GitHub PAT resolution — mirrors getAdoToken/MINIONS_ADO_TOKEN.
152
+ // Used at agent spawn time to inject GH_TOKEN for GitHub projects so child
153
+ // `gh`/`git push` calls authenticate as the right account without falling
154
+ // through to an interactive `gh auth login` device-code flow.
155
+ const ghToken = require('./engine/gh-token');
156
+
151
157
  // sanitizeBranch imported from shared.js
152
158
 
153
159
  // ─── Lifecycle (extracted to engine/lifecycle.js) ────────────────────────────
@@ -1681,6 +1687,27 @@ async function spawnAgent(dispatchItem, config) {
1681
1687
  const resolvedMaxBudget = shared.resolveAgentMaxBudget(agentConfig, engineConfig);
1682
1688
  const resolvedBare = shared.resolveAgentBareMode(agentConfig, engineConfig);
1683
1689
 
1690
+ // W-mpg6isvy000xca4d — On retry after FAILURE_CLASS.MODEL_UNAVAILABLE, swap
1691
+ // to the runtime-appropriate fallback model. Two paths gated on
1692
+ // `runtime.capabilities.fallbackModel` (no `runtime.name === ...` branches):
1693
+ // - Capability TRUE (Claude): the CLI's own --fallback-model handles
1694
+ // the swap on 429. We keep `engineConfig.claudeFallbackModel` plumbed
1695
+ // unconditionally via the fallbackModel opt below; no model override
1696
+ // needed at the engine layer.
1697
+ // - Capability FALSE (Copilot): no --fallback-model flag exists, so we
1698
+ // OVERRIDE the --model arg directly with engine.copilotFallbackModel
1699
+ // for this retry attempt only. The work item's _lastFailureClass is
1700
+ // written by dispatch.js's retry block; the next normal dispatch loop
1701
+ // pick-up re-reads it through meta.item.
1702
+ let effectiveModel = resolvedModel;
1703
+ const prevFailureClass = meta?.item?._lastFailureClass || null;
1704
+ if (prevFailureClass === FAILURE_CLASS.MODEL_UNAVAILABLE
1705
+ && runtime.capabilities?.fallbackModel === false
1706
+ && engineConfig.copilotFallbackModel) {
1707
+ effectiveModel = engineConfig.copilotFallbackModel;
1708
+ log('info', `MODEL_UNAVAILABLE retry: ${runtimeName} ${id} — overriding --model to ${effectiveModel}`);
1709
+ }
1710
+
1684
1711
  const requestedEffort = engineConfig.agentEffort || null;
1685
1712
 
1686
1713
  let cachedSessionId = null;
@@ -1698,7 +1725,7 @@ async function spawnAgent(dispatchItem, config) {
1698
1725
  }
1699
1726
 
1700
1727
  const args = _buildAgentSpawnFlags(runtime, {
1701
- model: resolvedModel,
1728
+ model: effectiveModel,
1702
1729
  maxTurns: _maxTurnsForType(type, engineConfig),
1703
1730
  allowedTools: claudeConfig.allowedTools,
1704
1731
  effort: requestedEffort,
@@ -1735,6 +1762,17 @@ async function spawnAgent(dispatchItem, config) {
1735
1762
  childEnv.MINIONS_KEEP_PROCESSES_SKIP_WORKDIR_CHECK = '1';
1736
1763
  }
1737
1764
 
1765
+ // W-mpg54mi2000n7b7e — suppress Git's interactive credential prompts and
1766
+ // Git Credential Manager's GUI dialog for every agent spawn. Without these,
1767
+ // a child `git push` against a private repo whose cached PAT expired pops a
1768
+ // Windows credential window the agent can never dismiss, hanging the
1769
+ // dispatch until the 5h agentTimeout wall-clock kill. Mirrors
1770
+ // shared.gitEnv() which already sets these for the engine's own git ops.
1771
+ // These vars are cheap and only take effect when git is invoked, so we set
1772
+ // them unconditionally regardless of repo host.
1773
+ childEnv.GIT_TERMINAL_PROMPT = '0';
1774
+ childEnv.GCM_INTERACTIVE = 'never';
1775
+
1738
1776
  if (getRepoHost(project) === 'ado') {
1739
1777
  // Inject cached ADO token so ADO agents skip re-authentication (#998).
1740
1778
  // getAdoToken() returns cached token (30-min TTL) or null — never blocks on browser auth.
@@ -1742,6 +1780,19 @@ async function spawnAgent(dispatchItem, config) {
1742
1780
  const adoToken = await getAdoToken();
1743
1781
  if (adoToken) childEnv.MINIONS_ADO_TOKEN = adoToken;
1744
1782
  } catch { /* non-fatal — agent can still authenticate on its own */ }
1783
+ } else if (getRepoHost(project) === 'github') {
1784
+ // W-mpg54mi2000n7b7e — inject a per-slug GitHub PAT so child `gh`/`git`
1785
+ // calls authenticate as the right account without any `gh auth login`
1786
+ // interactive flow. Resolution honors config.engine.ghAccounts via
1787
+ // engine/gh-token.js (exact owner → owner-glob → fleet default → null).
1788
+ // Mirrors the MINIONS_ADO_TOKEN injection above for ADO projects.
1789
+ try {
1790
+ const slug = shared.getProjectOrg(project) && project?.repoName
1791
+ ? `${shared.getProjectOrg(project)}/${project.repoName}`
1792
+ : null;
1793
+ const ghTok = slug ? ghToken.resolveTokenForSlug(slug) : null;
1794
+ if (ghTok) childEnv.GH_TOKEN = ghTok;
1795
+ } catch { /* non-fatal — agent can still authenticate on its own */ }
1745
1796
  }
1746
1797
 
1747
1798
  // Spawn via wrapper script — node directly (no bash intermediary)
@@ -2017,7 +2068,7 @@ async function spawnAgent(dispatchItem, config) {
2017
2068
  }
2018
2069
 
2019
2070
  const resumeArgs = _buildAgentSpawnFlags(runtime, {
2020
- model: resolvedModel,
2071
+ model: effectiveModel,
2021
2072
  maxTurns: engineConfig?.maxTurns || ENGINE_DEFAULTS.maxTurns,
2022
2073
  allowedTools: claudeConfig?.allowedTools,
2023
2074
  sessionId: steerSessionId,
@@ -2048,12 +2099,28 @@ async function spawnAgent(dispatchItem, config) {
2048
2099
  if (completionNonce) childEnv.MINIONS_COMPLETION_NONCE = completionNonce;
2049
2100
  childEnv.MINIONS_LIVE_OUTPUT_PATH = liveOutputPath;
2050
2101
  childEnv.MINIONS_REPO_HOST = getRepoHost(project);
2102
+ // W-mpg54mi2000n7b7e — same Git non-interactive guards as the initial
2103
+ // spawn path. Steering-resumed agents are equally susceptible to GCM
2104
+ // credential dialogs on `git push` against stale PATs.
2105
+ childEnv.GIT_TERMINAL_PROMPT = '0';
2106
+ childEnv.GCM_INTERACTIVE = 'never';
2051
2107
  if (getRepoHost(project) === 'ado') {
2052
2108
  // Inject cached ADO token for steering session too (#998)
2053
2109
  try {
2054
2110
  const adoToken = await getAdoToken();
2055
2111
  if (adoToken) childEnv.MINIONS_ADO_TOKEN = adoToken;
2056
2112
  } catch { /* non-fatal */ }
2113
+ } else if (getRepoHost(project) === 'github') {
2114
+ // W-mpg54mi2000n7b7e — same per-slug GH_TOKEN injection as the
2115
+ // initial spawn path so steering-resumed GitHub agents don't fall
2116
+ // through to an interactive `gh auth login` device-code flow.
2117
+ try {
2118
+ const slug = shared.getProjectOrg(project) && project?.repoName
2119
+ ? `${shared.getProjectOrg(project)}/${project.repoName}`
2120
+ : null;
2121
+ const ghTok = slug ? ghToken.resolveTokenForSlug(slug) : null;
2122
+ if (ghTok) childEnv.GH_TOKEN = ghTok;
2123
+ } catch { /* non-fatal */ }
2057
2124
  }
2058
2125
  // W-mp6k7ywi000fa33c — propagate keep_processes workdir-check override across steering resume.
2059
2126
  if (dispatchItem.meta?.item?.meta?.keep_processes_skip_workdir_check
@@ -4185,6 +4252,13 @@ async function discoverFromPrs(config, project) {
4185
4252
  // queued). Skip only the human-feedback dispatch path; leave
4186
4253
  // `fixDispatched=false` so downstream causes are still evaluated.
4187
4254
  const skipHumanFeedback = !!(lastHumanDispatch?.outcome === 'noop'
4255
+ // W-mpg6wptq0011cc68: indeterminate noops (detection could not verify
4256
+ // branch advance — fetch failed, worktree gone) must NOT permanently
4257
+ // suppress re-dispatch. lifecycle.js:2043-2048 explicitly comments
4258
+ // that a future tick with working detection must be free to re-fire.
4259
+ // The `_noOpFixes` count + `prNoOpFixPauseAttempts` (default 3) safety
4260
+ // valve still triggers pause if detection keeps failing.
4261
+ && !lastHumanDispatch.indeterminate
4188
4262
  && lastHumanDispatch.headSha
4189
4263
  && currentHeadSha
4190
4264
  && lastHumanDispatch.headSha === currentHeadSha
@@ -4369,6 +4443,12 @@ async function discoverFromPrs(config, project) {
4369
4443
  // below — symmetric to the human-feedback bug. Skip only the build-fix
4370
4444
  // dispatch path; downstream merge-conflict resolution must still run.
4371
4445
  const skipBuildFix = !!(lastBuildDispatch?.outcome === 'noop'
4446
+ // W-mpg6wptq0011cc68: symmetric protection — indeterminate noops here
4447
+ // (detection couldn't verify branch advance) must NOT permanently
4448
+ // suppress build-fix either. Same rationale as the human-feedback
4449
+ // guard above; the per-cause `_noOpFixes` count + pause-after-N valve
4450
+ // still applies.
4451
+ && !lastBuildDispatch.indeterminate
4372
4452
  && lastBuildDispatch.headSha
4373
4453
  && currentHeadSha
4374
4454
  && lastBuildDispatch.headSha === currentHeadSha);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2009",
3
+ "version": "0.1.2011",
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"