@yemi33/minions 0.1.738 → 0.1.740

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,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.740 (2026-04-09)
4
+
5
+ ### Features
6
+ - add prNumber field to pull-requests.json records (#711)
7
+
8
+ ## 0.1.739 (2026-04-09)
9
+
10
+ ### Fixes
11
+ - heartbeat feedback loop resets own mtime-based timeout (closes #724) (#728)
12
+
3
13
  ## 0.1.738 (2026-04-09)
4
14
 
5
15
  ### Fixes
package/engine/ado.js CHANGED
@@ -561,10 +561,14 @@ async function reconcilePrs(config) {
561
561
  const confirmedItemId = linkedItem ? linkedItemId : null;
562
562
 
563
563
  if (existingIds.has(prId)) {
564
+ const existing = existingPrs.find(p => p.id === prId);
565
+ // Backfill prNumber for existing records missing it
566
+ if (existing && existing.prNumber == null) {
567
+ existing.prNumber = adoPr.pullRequestId;
568
+ }
564
569
  // PR already tracked — write link to pr-links.json if we can extract an ID
565
570
  if (confirmedItemId) {
566
571
  addPrLink(prId, confirmedItemId);
567
- const existing = existingPrs.find(p => p.id === prId);
568
572
  if (existing && !(existing.prdItems || []).includes(confirmedItemId)) {
569
573
  existing.prdItems = Array.isArray(existing.prdItems) ? existing.prdItems : [];
570
574
  existing.prdItems.push(confirmedItemId);
@@ -582,6 +586,7 @@ async function reconcilePrs(config) {
582
586
  const prUrl = project.prUrlBase ? project.prUrlBase + adoPr.pullRequestId : '';
583
587
  existingPrs.push({
584
588
  id: prId,
589
+ prNumber: adoPr.pullRequestId,
585
590
  title: (adoPr.title || `PR #${adoPr.pullRequestId}`).slice(0, 120),
586
591
  agent: (linkedItem?.dispatched_to || adoPr.createdBy?.displayName || 'unknown').toLowerCase(),
587
592
  branch,
@@ -597,6 +602,14 @@ async function reconcilePrs(config) {
597
602
  log('info', `PR reconciliation: added ${prId} (branch: ${branch}, linked to ${confirmedItemId}) to ${project.name}`);
598
603
  }
599
604
 
605
+ // Backfill prNumber from pr.id for any PR missing it (e.g. created before prNumber was stored)
606
+ for (const pr of existingPrs) {
607
+ if (pr.prNumber == null) {
608
+ const derived = parseInt((pr.id || '').replace(/^PR-/, ''), 10);
609
+ if (derived) pr.prNumber = derived;
610
+ }
611
+ }
612
+
600
613
  // Backfill prdItems from pr-links for any PR with empty array
601
614
  const prLinks = shared.getPrLinks();
602
615
  let backfilled = 0;
package/engine/github.js CHANGED
@@ -562,9 +562,13 @@ async function reconcilePrs(config) {
562
562
  const confirmedItemId = linkedItem ? linkedItemId : null;
563
563
 
564
564
  if (existingIds.has(prId)) {
565
+ const existing = currentPrs.find(p => p.id === prId);
566
+ // Backfill prNumber for existing records missing it
567
+ if (existing && existing.prNumber == null) {
568
+ existing.prNumber = ghPr.number;
569
+ }
565
570
  if (confirmedItemId) {
566
571
  addPrLink(prId, confirmedItemId);
567
- const existing = currentPrs.find(p => p.id === prId);
568
572
  if (existing && !(existing.prdItems || []).includes(confirmedItemId)) {
569
573
  existing.prdItems = Array.isArray(existing.prdItems) ? existing.prdItems : [];
570
574
  existing.prdItems.push(confirmedItemId);
@@ -580,6 +584,7 @@ async function reconcilePrs(config) {
580
584
 
581
585
  currentPrs.push({
582
586
  id: prId,
587
+ prNumber: ghPr.number,
583
588
  title: (ghPr.title || `PR #${ghPr.number}`).slice(0, 120),
584
589
  agent: (linkedItem?.dispatched_to || ghPr.user?.login || 'unknown').toLowerCase(),
585
590
  branch,
@@ -596,6 +601,14 @@ async function reconcilePrs(config) {
596
601
  log('info', `GitHub PR reconciliation: added ${prId} (branch: ${branch}, linked to ${confirmedItemId}) to ${project.name}`);
597
602
  }
598
603
 
604
+ // Backfill prNumber from pr.id for any PR missing it (e.g. created before prNumber was stored)
605
+ for (const pr of currentPrs) {
606
+ if (pr.prNumber == null) {
607
+ const derived = parseInt((pr.id || '').replace(/^PR-/, ''), 10);
608
+ if (derived) pr.prNumber = derived;
609
+ }
610
+ }
611
+
599
612
  // Backfill prdItems from pr-links for any PR with empty array
600
613
  const prLinks = getPrLinks();
601
614
  let backfilled = 0;
@@ -666,6 +666,7 @@ function syncPrsFromOutput(output, agentId, meta, config) {
666
666
  prId, fullId,
667
667
  entry: {
668
668
  id: fullId,
669
+ prNumber: parseInt(prId, 10) || prId,
669
670
  title: (title || `PR created by ${agentName}`).slice(0, 120),
670
671
  agent: agentName,
671
672
  branch: meta?.branch || '',
package/engine/timeout.js CHANGED
@@ -159,11 +159,20 @@ function checkTimeouts(config) {
159
159
  const liveLogPath = path.join(AGENTS_DIR, item.agent, 'live-output.log');
160
160
  let lastActivity = item.started_at ? new Date(item.started_at).getTime() : 0;
161
161
 
162
- // Check live-output.log mtime as heartbeat
163
- try {
164
- const stat = fs.statSync(liveLogPath);
165
- lastActivity = Math.max(lastActivity, stat.mtimeMs);
166
- } catch { /* optional */ }
162
+ // For tracked processes, use realActivityMap (tracks actual agent stdout/stderr only,
163
+ // NOT engine heartbeat writes). This prevents the feedback loop where engine heartbeat
164
+ // writes to live-output.log reset the mtime that the timeout check reads (#724).
165
+ const realActivityMap = engine().realActivityMap;
166
+ if (hasProcess && realActivityMap?.has(item.id)) {
167
+ lastActivity = Math.max(lastActivity, realActivityMap.get(item.id));
168
+ } else {
169
+ // Orphan case (no tracked process): use live-output.log mtime as fallback.
170
+ // No heartbeat timer is running for orphans, so mtime is accurate.
171
+ try {
172
+ const stat = fs.statSync(liveLogPath);
173
+ lastActivity = Math.max(lastActivity, stat.mtimeMs);
174
+ } catch { /* optional */ }
175
+ }
167
176
 
168
177
  const silentMs = Date.now() - lastActivity;
169
178
  const silentSec = Math.round(silentMs / 1000);
package/engine.js CHANGED
@@ -142,6 +142,7 @@ const { runPostCompletionHooks, updateWorkItemStatus, syncPrdItemStatus, handleP
142
142
  // ─── Agent Spawner ──────────────────────────────────────────────────────────
143
143
 
144
144
  const activeProcesses = new Map(); // dispatchId → { proc, agentId, startedAt }
145
+ const realActivityMap = new Map(); // dispatchId → timestamp of last REAL agent output (not engine heartbeat)
145
146
  // tempAgents imported from engine/routing.js
146
147
  let engineRestartGraceUntil = 0; // timestamp — suppress orphan detection until this time
147
148
 
@@ -597,6 +598,7 @@ async function spawnAgent(dispatchItem, config) {
597
598
  proc.stdout.on('data', (data) => {
598
599
  const chunk = data.toString();
599
600
  lastOutputAt = Date.now();
601
+ realActivityMap.set(id, Date.now()); // Track real agent output separately from heartbeat
600
602
  if (stdout.length < MAX_OUTPUT) stdout += chunk.slice(0, MAX_OUTPUT - stdout.length);
601
603
  try { fs.appendFileSync(liveOutputPath, chunk); } catch { /* optional */ }
602
604
 
@@ -641,6 +643,7 @@ async function spawnAgent(dispatchItem, config) {
641
643
  proc.stderr.on('data', (data) => {
642
644
  const chunk = data.toString();
643
645
  lastOutputAt = Date.now();
646
+ realActivityMap.set(id, Date.now()); // Track real agent output separately from heartbeat
644
647
  if (stderr.length < MAX_OUTPUT) stderr += chunk.slice(0, MAX_OUTPUT - stderr.length);
645
648
  try { fs.appendFileSync(liveOutputPath, '[stderr] ' + chunk); } catch { /* optional */ }
646
649
  });
@@ -737,12 +740,14 @@ async function spawnAgent(dispatchItem, config) {
737
740
  resumeProc.stdout.on('data', (data) => {
738
741
  const chunk = data.toString();
739
742
  lastOutputAt = Date.now();
743
+ realActivityMap.set(id, Date.now()); // Track real agent output separately from heartbeat
740
744
  if (stdout.length < MAX_OUTPUT) stdout += chunk.slice(0, MAX_OUTPUT - stdout.length);
741
745
  try { fs.appendFileSync(liveOutputPath, chunk); } catch { /* optional */ }
742
746
  });
743
747
  resumeProc.stderr.on('data', (data) => {
744
748
  const chunk = data.toString();
745
749
  lastOutputAt = Date.now();
750
+ realActivityMap.set(id, Date.now()); // Track real agent output separately from heartbeat
746
751
  if (stderr.length < MAX_OUTPUT) stderr += chunk.slice(0, MAX_OUTPUT - stderr.length);
747
752
  try { fs.appendFileSync(liveOutputPath, '[stderr] ' + chunk); } catch { /* optional */ }
748
753
  });
@@ -773,6 +778,7 @@ async function spawnAgent(dispatchItem, config) {
773
778
  }
774
779
 
775
780
  activeProcesses.delete(id);
781
+ realActivityMap.delete(id); // Clean up real activity tracking
776
782
 
777
783
  // If timeout checker already finalized this dispatch, don't overwrite work-item status again.
778
784
  // This avoids races where close-handler marks an auto-retried item as failed.
@@ -880,6 +886,7 @@ async function spawnAgent(dispatchItem, config) {
880
886
  if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; }
881
887
  log('error', `Failed to spawn agent ${agentId}: ${err.message}`);
882
888
  activeProcesses.delete(id);
889
+ realActivityMap.delete(id); // Clean up real activity tracking
883
890
  completeDispatch(id, DISPATCH_RESULT.ERROR, `Spawn error: ${err.message}`);
884
891
  });
885
892
 
@@ -892,6 +899,7 @@ async function spawnAgent(dispatchItem, config) {
892
899
 
893
900
  // Track process — even if PID isn't available yet (async on Windows)
894
901
  activeProcesses.set(id, { proc, agentId, startedAt, sessionId: cachedSessionId });
902
+ realActivityMap.set(id, Date.now()); // Initialize real activity at spawn time
895
903
 
896
904
  updateAgentStatus(id, AGENT_STATUS.RUNNING, `Process spawned for ${agentId}`);
897
905
 
@@ -2957,7 +2965,7 @@ module.exports = {
2957
2965
 
2958
2966
  // Dispatch management (re-exported from engine/dispatch.js)
2959
2967
  mutateDispatch, addToDispatch, isRetryableFailureReason, completeDispatch, writeInboxAlert, updateAgentStatus,
2960
- activeProcesses, get engineRestartGraceUntil() { return engineRestartGraceUntil; },
2968
+ activeProcesses, realActivityMap, get engineRestartGraceUntil() { return engineRestartGraceUntil; },
2961
2969
  set engineRestartGraceUntil(v) { engineRestartGraceUntil = v; },
2962
2970
 
2963
2971
  // Agent lifecycle
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.738",
3
+ "version": "0.1.740",
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"