@yemi33/minions 0.1.600 → 0.1.601

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,8 +1,9 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.600 (2026-04-08)
3
+ ## 0.1.601 (2026-04-08)
4
4
 
5
5
  ### Fixes
6
+ - rotate live-output.log on spawn to preserve previous session (closes #543) (#551)
6
7
  - restore pendingFix on failed human-feedback dispatch (closes #540) (#550)
7
8
 
8
9
  ## 0.1.599 (2026-04-08)
package/dashboard.js CHANGED
@@ -1499,6 +1499,26 @@ const server = http.createServer(async (req, res) => {
1499
1499
  try {
1500
1500
  const stat = fs.statSync(liveLogPath);
1501
1501
  const fileSize = stat.size;
1502
+
1503
+ // Fall back to previous session log when current is sparse (fixes #543)
1504
+ const SPARSE_THRESHOLD = 500;
1505
+ if (fileSize < SPARSE_THRESHOLD) {
1506
+ const prevPath = path.join(agentDir, 'live-output-prev.log');
1507
+ try {
1508
+ const prevStat = fs.statSync(prevPath);
1509
+ if (prevStat.size > SPARSE_THRESHOLD) {
1510
+ const prevTailBytes = Math.min(tailBytes, prevStat.size);
1511
+ const prevStart = Math.max(0, prevStat.size - prevTailBytes);
1512
+ const prevFd = fs.openSync(prevPath, 'r');
1513
+ const prevBuf = Buffer.alloc(prevStat.size - prevStart);
1514
+ fs.readSync(prevFd, prevBuf, 0, prevBuf.length, prevStart);
1515
+ fs.closeSync(prevFd);
1516
+ const prevContent = prevBuf.toString('utf8');
1517
+ if (prevContent) safeWrite(`data: ${JSON.stringify(prevContent + '\n\n--- previous session (new session starting) ---\n\n')}\n\n`);
1518
+ }
1519
+ } catch { /* prev file may not exist — that's fine */ }
1520
+ }
1521
+
1502
1522
  if (fileSize > 0) {
1503
1523
  const readStart = Math.max(0, fileSize - tailBytes);
1504
1524
  const readLen = fileSize - readStart;
@@ -1565,7 +1585,9 @@ const server = http.createServer(async (req, res) => {
1565
1585
 
1566
1586
  async function handleAgentLive(req, res, match) {
1567
1587
  const agentId = match[1];
1568
- const livePath = path.join(MINIONS_DIR, 'agents', agentId, 'live-output.log');
1588
+ const agentDir = path.join(MINIONS_DIR, 'agents', agentId);
1589
+ const livePath = path.join(agentDir, 'live-output.log');
1590
+ const prevPath = path.join(agentDir, 'live-output-prev.log');
1569
1591
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
1570
1592
  res.setHeader('Access-Control-Allow-Origin', '*');
1571
1593
  const params = new URL(req.url, 'http://localhost').searchParams;
@@ -1576,6 +1598,28 @@ const server = http.createServer(async (req, res) => {
1576
1598
  try {
1577
1599
  const stat = fs.statSync(livePath);
1578
1600
  if (stat.size === 0) { res.end('No live output. Agent may not be running.'); return; }
1601
+
1602
+ // Fall back to previous session log when current is sparse (fixes #543)
1603
+ // Sparse = only header + init JSON, typically < 500 bytes
1604
+ const SPARSE_THRESHOLD = 500;
1605
+ if (stat.size < SPARSE_THRESHOLD && fs.existsSync(prevPath)) {
1606
+ try {
1607
+ const prevStat = fs.statSync(prevPath);
1608
+ if (prevStat.size > SPARSE_THRESHOLD) {
1609
+ // Prepend separator + previous session tail, then append current sparse content
1610
+ const prevTailBytes = Math.max(1, Math.min(tailBytes - stat.size - 100, prevStat.size));
1611
+ const prevStart = Math.max(0, prevStat.size - prevTailBytes);
1612
+ const prevBuf = Buffer.alloc(Math.min(prevTailBytes, prevStat.size));
1613
+ const prevFd = fs.openSync(prevPath, 'r');
1614
+ fs.readSync(prevFd, prevBuf, 0, prevBuf.length, prevStart);
1615
+ fs.closeSync(prevFd);
1616
+ const currentBuf = fs.readFileSync(livePath, 'utf8');
1617
+ res.end(prevBuf.toString('utf8') + '\n\n--- previous session (new session starting) ---\n\n' + currentBuf);
1618
+ return;
1619
+ }
1620
+ } catch { /* fall through to normal read */ }
1621
+ }
1622
+
1579
1623
  const start = Math.max(0, stat.size - tailBytes);
1580
1624
  const buf = Buffer.alloc(Math.min(tailBytes, stat.size));
1581
1625
  const fd = fs.openSync(livePath, 'r');
@@ -1583,6 +1627,21 @@ const server = http.createServer(async (req, res) => {
1583
1627
  fs.closeSync(fd);
1584
1628
  res.end(buf.toString('utf8'));
1585
1629
  } catch {
1630
+ // If live-output.log doesn't exist but prev does, serve that
1631
+ try {
1632
+ if (fs.existsSync(prevPath)) {
1633
+ const prevStat = fs.statSync(prevPath);
1634
+ if (prevStat.size > 0) {
1635
+ const start = Math.max(0, prevStat.size - tailBytes);
1636
+ const buf = Buffer.alloc(Math.min(tailBytes, prevStat.size));
1637
+ const fd = fs.openSync(prevPath, 'r');
1638
+ fs.readSync(fd, buf, 0, buf.length, start);
1639
+ fs.closeSync(fd);
1640
+ res.end(buf.toString('utf8') + '\n\n--- previous session (current session output unavailable) ---\n');
1641
+ return;
1642
+ }
1643
+ }
1644
+ } catch { /* fall through */ }
1586
1645
  res.end('No live output. Agent may not be running.');
1587
1646
  }
1588
1647
  return;
package/engine/cleanup.js CHANGED
@@ -76,7 +76,7 @@ function runCleanup(config, verbose = false) {
76
76
  }
77
77
  }
78
78
 
79
- // 2. Clean live-output.log for idle agents (not currently working)
79
+ // 2. Clean live-output.log and live-output-prev.log for idle agents (not currently working)
80
80
  for (const [agentId] of Object.entries(config.agents || {})) {
81
81
  const status = getAgentStatus(agentId);
82
82
  if (status.status !== 'working') {
@@ -90,6 +90,17 @@ function runCleanup(config, verbose = false) {
90
90
  }
91
91
  } catch { /* cleanup */ }
92
92
  }
93
+ // Also clean rotated previous session log
94
+ const prevPath = path.join(AGENTS_DIR, agentId, 'live-output-prev.log');
95
+ if (fs.existsSync(prevPath)) {
96
+ try {
97
+ const stat = fs.statSync(prevPath);
98
+ if (stat.mtimeMs < oneHourAgo) {
99
+ fs.unlinkSync(prevPath);
100
+ cleaned.liveOutputs++;
101
+ }
102
+ } catch { /* cleanup */ }
103
+ }
93
104
  }
94
105
  }
95
106
 
package/engine.js CHANGED
@@ -560,6 +560,20 @@ async function spawnAgent(dispatchItem, config) {
560
560
 
561
561
  // Live output file — written as data arrives so dashboard can tail it
562
562
  const liveOutputPath = path.join(AGENTS_DIR, agentId, 'live-output.log');
563
+
564
+ // Rotate previous live output to preserve session history (fixes #543: orphan recovery overwrites)
565
+ // Only rotate if the existing file has meaningful content (beyond just the header)
566
+ const LIVE_OUTPUT_SPARSE_THRESHOLD = 500; // bytes — header + init JSON is typically < 500
567
+ try {
568
+ if (fs.existsSync(liveOutputPath)) {
569
+ const prevStat = fs.statSync(liveOutputPath);
570
+ if (prevStat.size > LIVE_OUTPUT_SPARSE_THRESHOLD) {
571
+ const prevPath = path.join(AGENTS_DIR, agentId, 'live-output-prev.log');
572
+ fs.renameSync(liveOutputPath, prevPath);
573
+ }
574
+ }
575
+ } catch { /* rotation is best-effort — overwrite still happens below */ }
576
+
563
577
  safeWrite(liveOutputPath, `# Live output for ${agentId} — ${id}\n# Started: ${startedAt}\n# Task: ${dispatchItem.task}\n\n`);
564
578
 
565
579
  // Keep live log active even when the agent produces no stdout/stderr for long stretches.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.600",
3
+ "version": "0.1.601",
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"