@yemi33/minions 0.1.2298 → 0.1.2300

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/dashboard.js CHANGED
@@ -11444,25 +11444,19 @@ What would you like to discuss or change? When you're happy, say "approve" and I
11444
11444
 
11445
11445
  // W-mq03l6zh0006f0a1-d — read-only diagnostics surface for the per-org ADO
11446
11446
  // throttle tracker. Returns { orgs: { [orgBase]: { throttled, retryAfter,
11447
- // consecutiveHits } } }. Prefers the per-org getter ado.getAdoThrottleStateAll
11448
- // when present (introduced by W-mq03l6zh0006f0a1-b). Falls back to the
11449
- // process-global ado.getAdoThrottleState() under the synthetic key `global`
11450
- // when the per-org getter is not present, so the endpoint stays live across
11451
- // the staged rollout of the per-org isolation work.
11447
+ // consecutiveHits } } }. Uses getAdoThrottleState().perOrg, the per-org map
11448
+ // exposed by the aggregate getter (W-mq03l6zh0006f0a1-b).
11452
11449
  async function handleDiagnosticsAdoThrottle(req, res) {
11453
11450
  try {
11454
11451
  let orgs = {};
11455
- if (typeof ado.getAdoThrottleStateAll === 'function') {
11456
- const all = ado.getAdoThrottleStateAll() || {};
11452
+ if (typeof ado.getAdoThrottleState === 'function') {
11453
+ const all = ado.getAdoThrottleState().perOrg || {};
11457
11454
  // Defensive copy — handler must never expose internal mutable state.
11458
11455
  for (const [k, v] of Object.entries(all)) {
11459
11456
  if (v && typeof v === 'object') {
11460
11457
  orgs[k] = { throttled: !!v.throttled, retryAfter: Number(v.retryAfter) || 0, consecutiveHits: Number(v.consecutiveHits) || 0 };
11461
11458
  }
11462
11459
  }
11463
- } else if (typeof ado.getAdoThrottleState === 'function') {
11464
- const v = ado.getAdoThrottleState() || {};
11465
- orgs.global = { throttled: !!v.throttled, retryAfter: Number(v.retryAfter) || 0, consecutiveHits: Number(v.consecutiveHits) || 0 };
11466
11460
  }
11467
11461
  return jsonReply(res, 200, { orgs });
11468
11462
  } catch (e) { return jsonReply(res, e.statusCode || 500, { error: e.message }); }
package/engine/ado.js CHANGED
@@ -2683,14 +2683,6 @@ const getAdoThrottleState = (orgBase) => {
2683
2683
  return { throttled, retryAfter, consecutiveHits, perOrg };
2684
2684
  };
2685
2685
 
2686
- /** Returns the per-org tracker state map keyed by canonical orgBase. */
2687
- const getAdoThrottleStateAll = () => {
2688
- const out = {};
2689
- for (const [key, tracker] of _adoThrottlesByOrg) {
2690
- out[key] = tracker.getState();
2691
- }
2692
- return out;
2693
- };
2694
2686
 
2695
2687
  /**
2696
2688
  * Query ADO for an open PR on a specific branch.
@@ -2955,7 +2947,6 @@ module.exports = {
2955
2947
  _setAdoTokenAuthNoteWriterForTest, // exported for testing
2956
2948
  isAdoThrottled,
2957
2949
  getAdoThrottleState,
2958
- getAdoThrottleStateAll,
2959
2950
  fetchAdoPrMetadata,
2960
2951
  prExists, // issue #246 — confirm a loose ref points at a PR (not a work item) before stamping
2961
2952
  fetchSinglePrBuildStatus,
@@ -577,7 +577,7 @@ function isRetryableFailureReason(reason = '', failureClass = '') {
577
577
  FAILURE_CLASS.INVALID_MANAGED_SPAWN, // W-mpbhxg3b000u8411 — managed-spawn.json failed validation; re-running with the same wrong file won't fix it
578
578
  FAILURE_CLASS.MANAGED_SPAWN_HEALTHCHECK_FAILED, // W-mpbhxg3b000u8411 — healthcheck timed out; agent must fix the spec or the service it spawned
579
579
  FAILURE_CLASS.INJECTION_FLAGGED, // F5 (W-mpeklod3000we69c) — agent spotted a prompt-injection attempt in spliced untrusted content; a human must review the source before re-dispatch
580
- FAILURE_CLASS.LIVE_CHECKOUT_DIRTY, // P-a3f9b204 — live-checkout refused to spawn because operator localPath is dirty; mechanical retry won't fix it (operator must commit/stash/discard)
580
+ FAILURE_CLASS.LIVE_CHECKOUT_DIRTY, // P-a3f9b204 — live-checkout refused to spawn because operator localPath is dirty; mechanical retry won't fix it (operator must commit/stash/discard). NOTE (W-mqzmkoqt000hbca2): engine.js#spawnAgent ALWAYS passes an explicit `agentRetryable` for this class, so this Set entry is never the actual gate — when auto-cleanup (liveCheckoutAutoReset/AutoStash) is enabled the engine overrides to retryable. Kept as a defensive safety net for any caller that omits the explicit override.
581
581
  FAILURE_CLASS.LIVE_CHECKOUT_MID_OPERATION, // P-a7f3c1d9 — live-checkout refused to spawn because the operator tree is mid-operation (in-progress merge/rebase/cherry-pick/bisect or detached HEAD); mechanical retry won't fix it (operator must finish/abort the op or checkout a branch)
582
582
  FAILURE_CLASS.LIVE_CHECKOUT_BLOB_FETCH, // PL-live-checkout-reliability-hardening — live-checkout `git checkout <existing-branch>` failed hydrating the tree through the auth-less GVFS cache server (blobless partial clone, headless); deterministic, so mechanical retry just reproduces it (operator must hydrate the branch with their own creds, then re-dispatch)
583
583
  FAILURE_CLASS.OUTPUT_TRUNCATED, // P-8e4c2a17 — agent stdout exceeded the hard capture cap before the terminal result event; mechanical retry just reproduces the overflow (agent must reduce output volume or the task must be split)
@@ -3607,24 +3607,25 @@ function extractSkillsFromOutput(output, agentId, dispatchItem, config, runtimeN
3607
3607
  function updateAgentHistory(agentId, dispatchItem, result) {
3608
3608
 
3609
3609
  const historyPath = path.join(AGENTS_DIR, agentId, 'history.md');
3610
- let history = safeRead(historyPath) || '# Agent History\n\n';
3611
3610
  const entry = `### ${ts()} — ${result}\n` +
3612
3611
  `- **Task:** ${dispatchItem.task}\n` +
3613
3612
  `- **Type:** ${dispatchItem.type}\n` +
3614
3613
  `- **Project:** ${dispatchItem.meta?.project?.name || 'central'}\n` +
3615
3614
  `- **Branch:** ${dispatchItem.meta?.branch || 'none'}\n` +
3616
3615
  `- **Dispatch ID:** ${dispatchItem.id}\n\n`;
3617
- const headerEnd = history.indexOf('\n\n');
3618
- if (headerEnd >= 0) {
3619
- history = history.slice(0, headerEnd + 2) + entry + history.slice(headerEnd + 2);
3620
- } else {
3621
- history += entry;
3622
- }
3623
- const entries = history.split('### ').filter(Boolean);
3624
- const header = entries[0].startsWith('#') ? entries.shift() : '# Agent History\n\n';
3625
- const trimmed = entries.slice(0, 20);
3626
- history = header + trimmed.map(e => '### ' + e).join('');
3627
- shared.safeWrite(historyPath, history);
3616
+ shared.mutateTextFileLocked(historyPath, (history) => {
3617
+ history = history || '# Agent History\n\n';
3618
+ const headerEnd = history.indexOf('\n\n');
3619
+ if (headerEnd >= 0) {
3620
+ history = history.slice(0, headerEnd + 2) + entry + history.slice(headerEnd + 2);
3621
+ } else {
3622
+ history += entry;
3623
+ }
3624
+ const entries = history.split('### ').filter(Boolean);
3625
+ const header = entries[0].startsWith('#') ? entries.shift() : '# Agent History\n\n';
3626
+ const trimmed = entries.slice(0, 20);
3627
+ return header + trimmed.map(e => '### ' + e).join('');
3628
+ }, { defaultValue: '# Agent History\n\n' });
3628
3629
  log('info', `Updated history for ${agentId}`);
3629
3630
  }
3630
3631
 
package/engine.js CHANGED
@@ -2437,8 +2437,21 @@ async function spawnAgent(dispatchItem, config) {
2437
2437
  // burned all maxRetries. The dedicated counter is touched ONLY here (on a
2438
2438
  // dirty failure) and cleared on a successful prepareLiveCheckout below;
2439
2439
  // neither discovery nor the retry re-queue clears it.
2440
+ //
2441
+ // W-mqzmkoqt000hbca2 — AUTO-CLEANUP EXCEPTION: the two-strike cap assumes
2442
+ // the dirty tree is operator-owned and only a human can clear it. But when
2443
+ // `liveCheckoutAutoReset` or `liveCheckoutAutoStash` is configured (per-project
2444
+ // or fleet-wide), the engine itself re-cleans the tree on every dispatch
2445
+ // attempt, so a recurring dirty failure (e.g. a transiently failing
2446
+ // auto-reset/stash) is NOT operator-owned. In that case we skip the
2447
+ // two-strike cap and stay retryable — the next attempt re-runs auto-cleanup.
2448
+ // The normal `maxRetries` cap in dispatch.js still bounds the retries.
2440
2449
  const _priorDirtyAttempts = Number(dispatchItem.meta?.item?._liveCheckoutDirtyAttempts) || 0;
2441
2450
  const _alreadyDirtyFailed = _priorDirtyAttempts >= 1;
2451
+ const _autoCleanupEnabled =
2452
+ shared.resolveLiveCheckoutAutoReset(project, engineConfig) ||
2453
+ (typeof _liveCheckout.resolveLiveCheckoutAutoStash === 'function' &&
2454
+ !!_liveCheckout.resolveLiveCheckoutAutoStash({ project, engine: engineConfig }));
2442
2455
  const _alertBody = [
2443
2456
  '# Live-checkout refused: dirty worktree',
2444
2457
  '',
@@ -2457,9 +2470,11 @@ async function spawnAgent(dispatchItem, config) {
2457
2470
  ...(_dirtyFiles.length > 0 ? _dirtyFiles : ['(none reported)']),
2458
2471
  '```',
2459
2472
  '',
2460
- _alreadyDirtyFailed
2461
- ? 'Recovery: dirty state persisted across retries commit, stash, or discard the changes in the project checkout, then re-dispatch the work item.'
2462
- : 'This dispatch will be retried once automatically in case the dirty state is transient. If dirty again on retry, commit, stash, or discard the changes, then re-dispatch.',
2473
+ _autoCleanupEnabled
2474
+ ? 'Auto-cleanup (`liveCheckoutAutoReset`/`liveCheckoutAutoStash`) is active the engine re-cleans the tree on each attempt, so this dispatch will be retried automatically (subject to the normal retry cap).'
2475
+ : (_alreadyDirtyFailed
2476
+ ? 'Recovery: dirty state persisted across retries — commit, stash, or discard the changes in the project checkout, then re-dispatch the work item.'
2477
+ : 'This dispatch will be retried once automatically in case the dirty state is transient. If dirty again on retry, commit, stash, or discard the changes, then re-dispatch.'),
2463
2478
  ].join('\n');
2464
2479
  try { writeInboxAlert(`live-checkout-dirty-${_wiIdForAlert}`, _alertBody); }
2465
2480
  catch (e) { log('warn', `live-checkout: writeInboxAlert failed: ${e.message}`); }
@@ -2487,10 +2502,12 @@ async function spawnAgent(dispatchItem, config) {
2487
2502
  id,
2488
2503
  DISPATCH_RESULT.ERROR,
2489
2504
  _dirtyMsg.slice(0, 800),
2490
- _alreadyDirtyFailed
2491
- ? 'Live-checkout dirty state persisted across retriesoperator must commit/stash/discard before re-dispatch.'
2492
- : 'Live-checkout dirty tree retried once automatically; if dirty persists, operator must commit/stash/discard.',
2493
- { failureClass: FAILURE_CLASS.LIVE_CHECKOUT_DIRTY, agentRetryable: !_alreadyDirtyFailed },
2505
+ _autoCleanupEnabled
2506
+ ? 'Live-checkout dirty tree with auto-cleanup activewill retry (engine re-cleans each attempt; normal retry cap applies).'
2507
+ : (_alreadyDirtyFailed
2508
+ ? 'Live-checkout dirty state persisted across retries — operator must commit/stash/discard before re-dispatch.'
2509
+ : 'Live-checkout dirty tree retried once automatically; if dirty persists, operator must commit/stash/discard.'),
2510
+ { failureClass: FAILURE_CLASS.LIVE_CHECKOUT_DIRTY, agentRetryable: _autoCleanupEnabled || !_alreadyDirtyFailed },
2494
2511
  );
2495
2512
  cleanupTempAgent(agentId);
2496
2513
  return null;
@@ -5661,7 +5678,7 @@ function reconcileItemsWithPrs(items, allPrs, { onlyIds } = {}) {
5661
5678
  // ─── Inbox Consolidation (extracted to engine/consolidation.js) ──────────────
5662
5679
 
5663
5680
  const { consolidateInbox } = require('./engine/consolidation');
5664
- const { pollPrStatus, pollPrHumanComments, reconcilePrs, checkLiveReviewStatus: adoCheckLiveReview, checkLiveBuildAndConflict: adoCheckLiveBuildAndConflict, needsAdoPollRetry, getAdoToken, isAdoThrottled, getAdoThrottleStateAll } = require('./engine/ado');
5681
+ const { pollPrStatus, pollPrHumanComments, reconcilePrs, checkLiveReviewStatus: adoCheckLiveReview, checkLiveBuildAndConflict: adoCheckLiveBuildAndConflict, needsAdoPollRetry, getAdoToken, isAdoThrottled, getAdoThrottleState } = require('./engine/ado');
5665
5682
  const { pollPrStatus: ghPollPrStatus, pollPrHumanComments: ghPollPrHumanComments, reconcilePrs: ghReconcilePrs, checkLiveReviewStatus: ghCheckLiveReview, checkLiveBuildAndConflict: ghCheckLiveBuildAndConflict, isGhThrottled } = require('./engine/github');
5666
5683
  const { reconcileSharedBranchPrs } = require('./engine/shared-branch-pr-reconcile');
5667
5684
 
@@ -9770,7 +9787,7 @@ async function tickInner() {
9770
9787
  // Per-org throttle skip happens inside forEachActivePr (one log line per skipped project).
9771
9788
  // Top-level short-circuit: when every known ADO org is throttled, skip the whole phase
9772
9789
  // with one log line to avoid the per-project iteration cost.
9773
- const adoThrottleStates = getAdoThrottleStateAll() || {};
9790
+ const adoThrottleStates = getAdoThrottleState().perOrg || {};
9774
9791
  const adoOrgCount = Object.keys(adoThrottleStates).length;
9775
9792
  const allAdoThrottled = adoOrgCount > 0 && Object.values(adoThrottleStates).every(s => s && s.throttled);
9776
9793
  if (allAdoThrottled) {
@@ -9850,7 +9867,7 @@ async function tickInner() {
9850
9867
  // Per-org throttle skip happens inside forEachActivePr (one log line per skipped project).
9851
9868
  // Top-level short-circuit: when every known ADO org is throttled, skip the whole phase
9852
9869
  // with one log line to avoid the per-project iteration cost.
9853
- const adoThrottleStates = getAdoThrottleStateAll() || {};
9870
+ const adoThrottleStates = getAdoThrottleState().perOrg || {};
9854
9871
  const adoOrgCount = Object.keys(adoThrottleStates).length;
9855
9872
  const allAdoThrottled = adoOrgCount > 0 && Object.values(adoThrottleStates).every(s => s && s.throttled);
9856
9873
  if (allAdoThrottled) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2298",
3
+ "version": "0.1.2300",
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"