@yemi33/minions 0.1.2121 → 0.1.2123

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
@@ -123,7 +123,7 @@ const { mutateDispatch, addToDispatch, addToDispatchWithValidation, isRetryableF
123
123
 
124
124
  // ─── Timeout / Steering / Idle (extracted to engine/timeout.js) ──────────────
125
125
 
126
- const { checkTimeouts, checkSteering, checkIdleThreshold } = require('./engine/timeout');
126
+ const { checkTimeouts, checkSteering, checkIdleThreshold, dropSteeringForPurgedSession } = require('./engine/timeout');
127
127
  const steering = require('./engine/steering');
128
128
 
129
129
  // ─── Cleanup (extracted to engine/cleanup.js) ────────────────────────────────
@@ -528,6 +528,11 @@ function promoteCheckpointSteeringForClose(agentId, procInfo, runtime, liveOutpu
528
528
  const checkpointEntries = mergePendingSteeringEntries(pendingDeferred, lateCheckpoint);
529
529
  if (checkpointEntries.length === 0) {
530
530
  delete procInfo._deferredSteeringFiles;
531
+ // Gap B housekeeping: drop the per-entry deferred timestamps + stranded
532
+ // guard so a future spawn under the same procInfo (test harnesses + engine
533
+ // re-attach) starts with a clean slate.
534
+ delete procInfo._deferredSteeringQueuedAt;
535
+ delete procInfo._deferredSteeringStrandedFiles;
531
536
  return { status: 'none', entries: [] };
532
537
  }
533
538
 
@@ -548,6 +553,8 @@ function promoteCheckpointSteeringForClose(agentId, procInfo, runtime, liveOutpu
548
553
  procInfo._steeringEntry = checkpointEntries;
549
554
  procInfo._steeringDeferredCheckpoint = true;
550
555
  delete procInfo._deferredSteeringFiles;
556
+ delete procInfo._deferredSteeringQueuedAt;
557
+ delete procInfo._deferredSteeringStrandedFiles;
551
558
  // W-mq066js7000fff1f-a (Gap D): transition each promoted entry to
552
559
  // 're_spawning' — captures that the engine has committed to deliver
553
560
  // these messages via session resume at the natural checkpoint.
@@ -2683,9 +2690,14 @@ async function spawnAgent(dispatchItem, config) {
2683
2690
  updateAgentStatus(id, code === 0 ? AGENT_STATUS.FINISHED : AGENT_STATUS.FAILED,
2684
2691
  code === 0 ? 'Agent completed successfully' : `Agent exited with code ${code}`);
2685
2692
 
2686
- // Clear stale session if resume failed — prevents burning all retries on the same bad session
2693
+ // Clear stale session if resume failed — prevents burning all retries on the same bad session.
2694
+ // W-mq066js7000fff1f-c (Gap G): drop any in-flight steering-store entries
2695
+ // bound to the purged session BEFORE unlinking session.json, so the human
2696
+ // sees a [steering-failed] line + can re-send instead of silently watching
2697
+ // their message strand against a dead session.
2687
2698
  if (code !== 0 && cachedSessionId && stderr.includes('No conversation found')) {
2688
2699
  log('warn', `Stale session ${cachedSessionId} for ${agentId} — clearing session.json`);
2700
+ try { dropSteeringForPurgedSession(agentId, cachedSessionId, liveOutputPath); } catch {}
2689
2701
  try { shared.safeUnlink(path.join(AGENTS_DIR, agentId, 'session.json')); } catch {}
2690
2702
  }
2691
2703
 
@@ -3932,7 +3944,7 @@ function reconcileItemsWithPrs(items, allPrs, { onlyIds } = {}) {
3932
3944
  // ─── Inbox Consolidation (extracted to engine/consolidation.js) ──────────────
3933
3945
 
3934
3946
  const { consolidateInbox } = require('./engine/consolidation');
3935
- const { pollPrStatus, pollPrHumanComments, reconcilePrs, checkLiveReviewStatus: adoCheckLiveReview, checkLiveBuildAndConflict: adoCheckLiveBuildAndConflict, needsAdoPollRetry, getAdoToken, isAdoThrottled } = require('./engine/ado');
3947
+ const { pollPrStatus, pollPrHumanComments, reconcilePrs, checkLiveReviewStatus: adoCheckLiveReview, checkLiveBuildAndConflict: adoCheckLiveBuildAndConflict, needsAdoPollRetry, getAdoToken, isAdoThrottled, getAdoThrottleStateAll } = require('./engine/ado');
3936
3948
  const { pollPrStatus: ghPollPrStatus, pollPrHumanComments: ghPollPrHumanComments, reconcilePrs: ghReconcilePrs, checkLiveReviewStatus: ghCheckLiveReview, checkLiveBuildAndConflict: ghCheckLiveBuildAndConflict, isGhThrottled } = require('./engine/github');
3937
3949
 
3938
3950
  // ─── State Snapshot ─────────────────────────────────────────────────────────
@@ -6866,12 +6878,35 @@ async function discoverWork(config) {
6866
6878
  mutateJsonFileLocked(centralPath, (items) => {
6867
6879
  if (!Array.isArray(items)) items = [];
6868
6880
  let added = 0;
6881
+ // Snapshot active dedup keys BEFORE the loop so multiple items in the
6882
+ // same harness mission (same _missionId) all land in one tick. Without
6883
+ // this snapshot, the first item's push would block subsequent items
6884
+ // in the same mission from joining (W-mq07a9gf000jbc2b — tri-agent
6885
+ // harness mode requires Planner+Generator+Evaluator to land together).
6886
+ const activeMissionIds = new Set();
6887
+ const activeScheduleIds = new Set();
6888
+ for (const existing of items) {
6889
+ if (existing.status === WI_STATUS.DONE || existing.status === WI_STATUS.FAILED) continue;
6890
+ if (existing._missionId) activeMissionIds.add(existing._missionId);
6891
+ if (existing._scheduleId) activeScheduleIds.add(existing._scheduleId);
6892
+ }
6893
+ const addedScheduleIdsThisTick = new Set();
6869
6894
  for (const item of taskItems) {
6870
- if (!items.some(i => i._scheduleId === item._scheduleId && i.status !== WI_STATUS.DONE && i.status !== WI_STATUS.FAILED)) {
6871
- items.push(item);
6872
- added++;
6873
- log('info', `Scheduled task fired: ${item._scheduleId} ${item.title}`);
6895
+ // Mission items dedup by _missionId against pre-existing rows only
6896
+ // (the trio's other items added later in this loop must not block
6897
+ // each other). Plain scheduled items keep the original scheduleId
6898
+ // dedup AND skip if a sibling item from the same tick already
6899
+ // claimed the schedule slot.
6900
+ if (item._missionId) {
6901
+ if (activeMissionIds.has(item._missionId)) continue;
6902
+ } else {
6903
+ if (activeScheduleIds.has(item._scheduleId)) continue;
6904
+ if (addedScheduleIdsThisTick.has(item._scheduleId)) continue;
6874
6905
  }
6906
+ items.push(item);
6907
+ if (!item._missionId && item._scheduleId) addedScheduleIdsThisTick.add(item._scheduleId);
6908
+ added++;
6909
+ log('info', `Scheduled task fired: ${item._scheduleId} → ${item.title}`);
6875
6910
  }
6876
6911
  return items;
6877
6912
  }, { defaultValue: [] });
@@ -7337,10 +7372,18 @@ async function tickInner() {
7337
7372
  lastPrStatusPollAt = now;
7338
7373
  // Build promise array — enabled+unthrottled polls run concurrently via Promise.allSettled
7339
7374
  const statusPolls = [];
7340
- if (adoPollEnabled && !isAdoThrottled()) {
7341
- statusPolls.push(pollPrStatus(config).catch(err => { log('warn', `ADO PR status poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }));
7342
- } else if (adoPollEnabled && isAdoThrottled()) {
7343
- log('info', '[ado] PR status poll skipped throttled');
7375
+ if (adoPollEnabled) {
7376
+ // Per-org throttle skip happens inside forEachActivePr (one log line per skipped project).
7377
+ // Top-level short-circuit: when every known ADO org is throttled, skip the whole phase
7378
+ // with one log line to avoid the per-project iteration cost.
7379
+ const adoThrottleStates = getAdoThrottleStateAll() || {};
7380
+ const adoOrgCount = Object.keys(adoThrottleStates).length;
7381
+ const allAdoThrottled = adoOrgCount > 0 && Object.values(adoThrottleStates).every(s => s && s.throttled);
7382
+ if (allAdoThrottled) {
7383
+ log('info', `[ado] PR status poll skipped — all ${adoOrgCount} known orgs throttled`);
7384
+ } else {
7385
+ statusPolls.push(pollPrStatus(config).catch(err => { log('warn', `ADO PR status poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }));
7386
+ }
7344
7387
  }
7345
7388
  if (ghPollEnabled && !isGhThrottled()) {
7346
7389
  statusPolls.push(ghPollPrStatus(config).catch(err => { log('warn', `GitHub PR status poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }));
@@ -7383,10 +7426,18 @@ async function tickInner() {
7383
7426
  lastPrCommentsPollAt = now;
7384
7427
  // Build promise array — enabled+unthrottled comment polls run concurrently via Promise.allSettled
7385
7428
  const commentPolls = [];
7386
- if (adoPollEnabled && !isAdoThrottled()) {
7387
- commentPolls.push(pollPrHumanComments(config).catch(err => { log('warn', `ADO PR comment poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }));
7388
- } else if (adoPollEnabled && isAdoThrottled()) {
7389
- log('info', '[ado] PR comment poll skipped throttled');
7429
+ if (adoPollEnabled) {
7430
+ // Per-org throttle skip happens inside forEachActivePr (one log line per skipped project).
7431
+ // Top-level short-circuit: when every known ADO org is throttled, skip the whole phase
7432
+ // with one log line to avoid the per-project iteration cost.
7433
+ const adoThrottleStates = getAdoThrottleStateAll() || {};
7434
+ const adoOrgCount = Object.keys(adoThrottleStates).length;
7435
+ const allAdoThrottled = adoOrgCount > 0 && Object.values(adoThrottleStates).every(s => s && s.throttled);
7436
+ if (allAdoThrottled) {
7437
+ log('info', `[ado] PR comment poll skipped — all ${adoOrgCount} known orgs throttled`);
7438
+ } else {
7439
+ commentPolls.push(pollPrHumanComments(config).catch(err => { log('warn', `ADO PR comment poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }));
7440
+ }
7390
7441
  }
7391
7442
  if (ghPollEnabled && !isGhThrottled()) {
7392
7443
  commentPolls.push(ghPollPrHumanComments(config).catch(err => { log('warn', `GitHub PR comment poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2121",
3
+ "version": "0.1.2123",
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"