@yemi33/minions 0.1.1568 → 0.1.1569

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,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1569 (2026-04-27)
4
+
5
+ ### Fixes
6
+ - avoid duplicate review retry loops
7
+
3
8
  ## 0.1.1568 (2026-04-27)
4
9
 
5
10
  ### Fixes
@@ -892,6 +892,23 @@ function parseReviewVerdict(text) {
892
892
  return null;
893
893
  }
894
894
 
895
+ /**
896
+ * Detect "idempotent bailout" output from a review agent — the agent saw a
897
+ * prior review on the PR (or the same dispatchKey re-fired) and chose to bail
898
+ * rather than spam a duplicate comment.
899
+ *
900
+ * Such output is intentionally short and contains no VERDICT keyword. Treating
901
+ * it as a retryable failure burns _retryCount and eventually flips the WI to
902
+ * status=failed even though the original review was successfully posted (#1770).
903
+ *
904
+ * @param {string} text - Agent output / resultSummary
905
+ * @returns {boolean}
906
+ */
907
+ function isReviewBailout(text) {
908
+ if (!text || typeof text !== 'string') return false;
909
+ return /bail(ing)?\s+out/i.test(text) || /already\s+posted/i.test(text);
910
+ }
911
+
895
912
  async function updatePrAfterReview(agentId, pr, project, config, resultSummary) {
896
913
 
897
914
  if (!pr?.id) return;
@@ -1657,9 +1674,18 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
1657
1674
  // same pattern as plan-to-prd (#893): updateWorkItemStatus deletes _retryCount, so the check
1658
1675
  // must read/increment it before that happens. Also sets skipDoneStatus so completedAt isn't
1659
1676
  // written and then left dangling when status is reset to pending for retry.
1677
+ //
1678
+ // (#1770) Idempotent bailout: if the agent explicitly bailed because a review was
1679
+ // already posted (e.g. the WI got re-dispatched before lifecycle marked the first
1680
+ // run done), treat the run as success — fall through to mark DONE without retry.
1681
+ // Without this, the second run produces no VERDICT, _retryCount increments,
1682
+ // and after 3 such bailouts the WI flips to status=failed even though the
1683
+ // original review was posted on the first run.
1660
1684
  if (effectiveSuccess && type === WORK_TYPE.REVIEW && meta?.item?.id) {
1661
1685
  const verdict = parseReviewVerdict(resultSummary);
1662
- if (!verdict) {
1686
+ if (!verdict && isReviewBailout(resultSummary)) {
1687
+ log('info', `Review ${meta.item.id} bailed out (review already posted) — treating as DONE without retry`);
1688
+ } else if (!verdict) {
1663
1689
  skipDoneStatus = true;
1664
1690
  const wiPath = resolveWorkItemPath(meta);
1665
1691
  if (wiPath) {
@@ -1672,6 +1698,8 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
1672
1698
  if (retries < ENGINE_DEFAULTS.maxRetries) {
1673
1699
  w.status = WI_STATUS.PENDING;
1674
1700
  w._retryCount = retries + 1;
1701
+ w._lastRetryAt = ts();
1702
+ w._lastRetryReason = 'no review verdict';
1675
1703
  delete w.dispatched_at;
1676
1704
  delete w.completedAt;
1677
1705
  log('warn', `Review ${meta.item.id} completed without verdict — auto-retry ${retries + 1}/${ENGINE_DEFAULTS.maxRetries}`);
@@ -2162,6 +2190,7 @@ module.exports = {
2162
2190
  updateMetrics,
2163
2191
  parseAgentOutput,
2164
2192
  parseReviewVerdict,
2193
+ isReviewBailout,
2165
2194
  parseStructuredCompletion,
2166
2195
  runPostCompletionHooks,
2167
2196
  syncPrdFromPrs,
package/engine/shared.js CHANGED
@@ -758,6 +758,7 @@ const ENGINE_DEFAULTS = {
758
758
  evalMaxIterations: 3, // max review→fix cycles before escalating to human
759
759
  evalMaxCost: null, // USD ceiling per work item across all eval iterations; null = no limit (gather baseline data first)
760
760
  maxRetries: 3, // max dispatch retries before marking work item as failed
761
+ minRetryGapMs: 120000, // 2min — minimum gap between retry dispatches for the same work item; prevents tight retry loops when an idempotent agent (e.g. review bailing out on a duplicate) cannot produce the expected output (#1770)
761
762
  pipelineApiRetries: 2, // max attempts for pipeline API calls
762
763
  pipelineApiRetryDelay: 2000, // ms delay between pipeline API retries
763
764
  versionCheckInterval: 3600000, // 1 hour — how often to check npm for updates (ms)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1568",
3
+ "version": "0.1.1569",
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"