@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 +10 -0
- package/engine/ado.js +14 -1
- package/engine/github.js +14 -1
- package/engine/lifecycle.js +1 -0
- package/engine/timeout.js +14 -5
- package/engine.js +9 -1
- package/package.json +1 -1
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;
|
package/engine/lifecycle.js
CHANGED
|
@@ -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
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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.
|
|
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"
|