@yemi33/minions 0.1.461 → 0.1.463

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,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.461 (2026-04-07)
3
+ ## 0.1.463 (2026-04-07)
4
4
 
5
5
  ### Fixes
6
+ - clear stale session.json on resume failure, hang kill, and orphan
7
+
8
+ ## 0.1.462 (2026-04-07)
9
+
10
+ ### Fixes
11
+ - show 'shared branch' label in work item PR column when no PR yet
6
12
  - plan card click opens modal immediately with loading state
7
13
  - remove non-functional Discuss & Revise button from plan cards
8
14
  - steering no longer causes pending/active flip
@@ -37,7 +37,9 @@ function wiRow(item) {
37
37
  const priBadge = (p) => '<span class="prd-item-priority ' + (p || '') + '">' + escHtml(p || 'medium') + '</span>';
38
38
  const prLink = item._pr
39
39
  ? '<a class="pr-title" href="' + escHtml(item._prUrl || '#') + '" target="_blank" style="font-size:10px">' + escHtml(item._pr) + '</a>'
40
- : '<span style="color:var(--muted)">—</span>';
40
+ : (item.branchStrategy === 'shared-branch' && item.status === 'done')
41
+ ? '<span style="font-size:9px;color:var(--muted)" title="Part of shared branch — aggregate PR created at verify stage">shared branch</span>'
42
+ : '<span style="color:var(--muted)">—</span>';
41
43
  return '<tr style="cursor:pointer" onclick="openWorkItemDetail(\'' + escHtml(item.id) + '\')">' +
42
44
  '<td><span class="pr-id">' + escHtml(item.id || '') + '</span></td>' +
43
45
  '<td style="max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml((item.title || '').slice(0, 200)) + '">' + escHtml(item.title || '') + '</td>' +
package/dashboard.js CHANGED
@@ -844,6 +844,9 @@ function cleanDispatchEntries(matchFn) {
844
844
  const engineDir = path.join(MINIONS_DIR, 'engine');
845
845
  try {
846
846
  let removed = 0;
847
+ // Collect PIDs and file paths inside the lock, execute kills outside
848
+ const pidsToKill = [];
849
+ const filesToDelete = [];
847
850
  mutateJsonFileLocked(dispatchPath, (dispatch) => {
848
851
  dispatch.pending = Array.isArray(dispatch.pending) ? dispatch.pending : [];
849
852
  dispatch.active = Array.isArray(dispatch.active) ? dispatch.active : [];
@@ -853,17 +856,16 @@ function cleanDispatchEntries(matchFn) {
853
856
  if (queue === 'active') {
854
857
  for (const d of dispatch[queue]) {
855
858
  if (!matchFn(d)) continue;
856
- // Kill the running agent process via PID file
859
+ // Collect PID and cleanup paths actual I/O happens after lock release
857
860
  const pidFile = path.join(engineDir, `pid-${d.id}.pid`);
858
861
  try {
859
862
  const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim());
860
- if (pid) process.kill(pid, 'SIGTERM');
861
- } catch { /* process may be dead */ }
862
- try { fs.unlinkSync(pidFile); } catch { /* cleanup */ }
863
- // Clean up temp prompt files
864
- try { fs.unlinkSync(path.join(engineDir, 'tmp', `prompt-${d.id}.md`)); } catch { /* cleanup */ }
865
- try { fs.unlinkSync(path.join(engineDir, 'tmp', `sysprompt-${d.id}.md`)); } catch { /* cleanup */ }
866
- try { fs.unlinkSync(path.join(engineDir, 'tmp', `sysprompt-${d.id}.md.tmp`)); } catch { /* cleanup */ }
863
+ if (pid) pidsToKill.push(pid);
864
+ } catch { /* PID file may not exist */ }
865
+ filesToDelete.push(pidFile);
866
+ filesToDelete.push(path.join(engineDir, 'tmp', `prompt-${d.id}.md`));
867
+ filesToDelete.push(path.join(engineDir, 'tmp', `sysprompt-${d.id}.md`));
868
+ filesToDelete.push(path.join(engineDir, 'tmp', `sysprompt-${d.id}.md.tmp`));
867
869
  }
868
870
  }
869
871
  dispatch[queue] = dispatch[queue].filter(d => !matchFn(d));
@@ -871,6 +873,22 @@ function cleanDispatchEntries(matchFn) {
871
873
  }
872
874
  return dispatch;
873
875
  }, { defaultValue: { pending: [], active: [], completed: [] } });
876
+ // Kill processes outside the lock — these can take hundreds of ms on Windows
877
+ for (const pid of pidsToKill) {
878
+ try {
879
+ const safePid = shared.validatePid(pid);
880
+ if (process.platform === 'win32') {
881
+ const { execFileSync } = require('child_process');
882
+ execFileSync('taskkill', ['/PID', String(safePid), '/T'], { stdio: 'pipe', timeout: 5000, windowsHide: true });
883
+ } else {
884
+ process.kill(safePid, 'SIGTERM');
885
+ }
886
+ } catch { /* process may already be dead */ }
887
+ }
888
+ // Clean up files outside the lock
889
+ for (const fp of filesToDelete) {
890
+ try { fs.unlinkSync(fp); } catch { /* file may not exist */ }
891
+ }
874
892
  return removed;
875
893
  } catch { return 0; }
876
894
  }
package/engine/timeout.js CHANGED
@@ -211,6 +211,8 @@ function checkTimeouts(config) {
211
211
  if (!hasProcess && silentMs > effectiveTimeout && Date.now() > engineRestartGraceUntil) {
212
212
  // No tracked process AND no recent output past effective timeout AND grace period expired → orphaned
213
213
  log('warn', `Orphan detected: ${item.agent} (${item.id}) — no process tracked, silent for ${silentSec}s${isBlocking ? ' (blocking timeout exceeded)' : ''}`);
214
+ // Clear session so retry starts fresh
215
+ try { shared.safeUnlink(path.join(AGENTS_DIR, item.agent, 'session.json')); } catch {}
214
216
  deadItems.push({ item, reason: `Orphaned — no process, silent for ${silentSec}s` });
215
217
  } else if (hasProcess && silentMs > effectiveTimeout) {
216
218
  // Has process but no output past effective timeout → hung
@@ -220,6 +222,8 @@ function checkTimeouts(config) {
220
222
  shared.killGracefully(procInfo.proc, 5000);
221
223
  activeProcesses.delete(item.id);
222
224
  }
225
+ // Clear session so retry starts fresh instead of resuming the killed session
226
+ try { shared.safeUnlink(path.join(AGENTS_DIR, item.agent, 'session.json')); } catch {}
223
227
  deadItems.push({ item, reason: `Hung — no output for ${silentSec}s` });
224
228
  }
225
229
  // If has process and recent output → healthy, let it run
package/engine.js CHANGED
@@ -562,6 +562,12 @@ function spawnAgent(dispatchItem, config) {
562
562
  if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; }
563
563
  log('info', `Agent ${agentId} (${id}) exited with code ${code}`);
564
564
 
565
+ // Clear stale session if resume failed — prevents burning all retries on the same bad session
566
+ if (code !== 0 && cachedSessionId && stderr.includes('No conversation found')) {
567
+ log('warn', `Stale session ${cachedSessionId} for ${agentId} — clearing session.json`);
568
+ try { shared.safeUnlink(path.join(AGENTS_DIR, agentId, 'session.json')); } catch {}
569
+ }
570
+
565
571
  // Check if this was a steering kill — re-spawn with resume
566
572
  const procInfo = activeProcesses.get(id);
567
573
  if (procInfo?._steeringMessage) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.461",
3
+ "version": "0.1.463",
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"