@yemi33/minions 0.1.729 → 0.1.730

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.729 (2026-04-09)
3
+ ## 0.1.730 (2026-04-09)
4
4
 
5
5
  ### Features
6
+ - Per-type heartbeat timeouts for read-heavy work types
6
7
  - playbook improvements — soften CAPS, add context-awareness, restructure decompose output
7
8
  - pipeline auto-detect — skip wait/plan stages when artifacts exist
8
9
  - replace raw status strings with WI_STATUS constants in dashboard.js
@@ -15,6 +16,7 @@
15
16
  - make post-verify plan archiving opt-in via autoArchive config
16
17
 
17
18
  ### Fixes
19
+ - show 'queued' instead of 'already dispatched' for pending work items
18
20
  - resolve 3 failing unit tests on PR-661
19
21
  - repair 3 pre-existing test failures in source-pattern tests
20
22
  - merge master, sync dashboard-build jsFiles, bump HTML size limit
@@ -48,7 +48,8 @@ function wiRow(item) {
48
48
  '<td>' + typeBadge(item.type) + '</td>' +
49
49
  '<td>' + priBadge(item.priority) + '</td>' +
50
50
  '<td>' + statusBadge(item.status || 'pending') +
51
- (item._pendingReason && item.status === 'pending' ? ' <span style="font-size:9px;color:var(--muted);margin-left:4px" title="Pending reason: ' + escHtml(item._pendingReason) + '">' + escHtml(item._pendingReason.replace(/_/g, ' ')) + '</span>' : '') +
51
+ (item._pendingReason && item.status === 'pending' && item._pendingReason !== 'already_dispatched' ? ' <span style="font-size:9px;color:var(--muted);margin-left:4px" title="Pending reason: ' + escHtml(item._pendingReason) + '">' + escHtml(item._pendingReason.replace(/_/g, ' ')) + '</span>' : '') +
52
+ (item._pendingReason === 'already_dispatched' && item.status === 'pending' ? ' <span style="font-size:9px;color:var(--blue);margin-left:4px" title="In dispatch queue, waiting to be assigned">queued</span>' : '') +
52
53
  (item._skipReason && item.status === 'pending' ? ' <span style="font-size:9px;color:var(--yellow);margin-left:4px" title="Dispatch blocked: ' + escHtml(item._skipReason) + (item._blockedBy ? ' (by ' + escHtml(item._blockedBy) + ')' : '') + '">' + escHtml(item._skipReason.replace(/_/g, ' ')) + (item._blockedBy ? ' <span style="color:var(--muted)">(' + escHtml(item._blockedBy) + ')</span>' : '') + '</span>' : '') +
53
54
  (item.status === 'failed' ? ' ' + wiRetryBtn(item) : '') +
54
55
  '</td>' +
@@ -440,7 +441,7 @@ function openWorkItemDetail(id) {
440
441
  if (item.dispatched_at) html += field('Dispatched', escHtml(new Date(item.dispatched_at).toLocaleString()) + ' to ' + escHtml(item.dispatched_to || '?'));
441
442
  if (item.completedAt) html += field('Completed', escHtml(new Date(item.completedAt).toLocaleString()));
442
443
  if (item.failReason) html += field('Failure Reason', '<span style="color:var(--red)">' + escHtml(item.failReason) + '</span>');
443
- if (item._pendingReason && item.status === 'pending') html += field('Pending Reason', escHtml(item._pendingReason.replace(/_/g, ' ')));
444
+ if (item._pendingReason && item.status === 'pending') html += field('Pending Reason', item._pendingReason === 'already_dispatched' ? 'Queued — waiting for available agent slot' : escHtml(item._pendingReason.replace(/_/g, ' ')));
444
445
  if (item._skipReason && item.status === 'pending') html += field('Dispatch Blocked', '<span style="color:var(--yellow)">' + escHtml(item._skipReason.replace(/_/g, ' ')) + '</span>' + (item._blockedBy ? ' — blocked by <strong>' + escHtml(item._blockedBy) + '</strong>' : ''));
445
446
  if (item.depends_on?.length) html += field('Depends On', item.depends_on.map(d => '<code>' + escHtml(d) + '</code>').join(', '));
446
447
  if (item.acceptanceCriteria?.length) html += field('Acceptance Criteria', '<ul style="margin:0;padding-left:20px">' + item.acceptanceCriteria.map(c => '<li>' + escHtml(c) + '</li>').join('') + '</ul>');
package/engine/shared.js CHANGED
@@ -518,7 +518,8 @@ const ENGINE_DEFAULTS = {
518
518
  maxConcurrent: 5,
519
519
  inboxConsolidateThreshold: 5,
520
520
  agentTimeout: 18000000, // 5h
521
- heartbeatTimeout: 300000, // 5min
521
+ heartbeatTimeout: 300000, // 5min — base heartbeat for most work types
522
+ heartbeatTimeouts: {}, // per-type overrides; merged with defaults at runtime (see timeout.js)
522
523
  maxTurns: 100,
523
524
  worktreeCreateTimeout: 300000, // 5min for git worktree add on large Windows repos
524
525
  worktreeCreateRetries: 1, // retry once on transient timeout/lock races
package/engine/timeout.js CHANGED
@@ -132,6 +132,9 @@ function checkTimeouts(config) {
132
132
  const timeout = config.engine?.agentTimeout || DEFAULTS.agentTimeout;
133
133
  const heartbeatTimeout = config.engine?.heartbeatTimeout || DEFAULTS.heartbeatTimeout;
134
134
 
135
+ // Per-type heartbeat timeouts: merge defaults ← ENGINE_DEFAULTS ← config overrides
136
+ const perTypeTimeouts = { ...DEFAULT_HEARTBEAT_TIMEOUTS, ...DEFAULTS.heartbeatTimeouts, ...(config.engine?.heartbeatTimeouts || {}) };
137
+
135
138
  // 1. Check tracked processes for hard timeout (supports per-item deadline from fan-out)
136
139
  for (const [id, info] of activeProcesses.entries()) {
137
140
  const itemTimeout = info.meta?.deadline ? Math.max(0, info.meta.deadline - new Date(info.startedAt).getTime()) : timeout;
@@ -195,13 +198,16 @@ function checkTimeouts(config) {
195
198
  }
196
199
  } catch (e) { log('warn', 'output completion detection: ' + e.message); }
197
200
 
201
+ // Resolve per-type heartbeat timeout: per-type map → base heartbeatTimeout fallback
202
+ const itemHeartbeat = perTypeTimeouts[item.type] || heartbeatTimeout;
203
+
198
204
  // Check if agent is in a blocking tool call (TaskOutput block:true, Bash with long timeout, etc.)
199
205
  // These tools produce no stdout for extended periods — don't kill them prematurely
200
206
  // Check for BOTH tracked and untracked processes (orphan case after engine restart)
201
207
  let isBlocking = false;
202
- let blockingTimeout = heartbeatTimeout;
208
+ let blockingTimeout = itemHeartbeat;
203
209
  let blockingTool = '';
204
- if (silentMs > heartbeatTimeout) {
210
+ if (silentMs > itemHeartbeat) {
205
211
  try {
206
212
  const liveLog = safeRead(liveLogPath);
207
213
  if (liveLog) {
@@ -219,7 +225,7 @@ function checkTimeouts(config) {
219
225
  // TaskOutput with block:true — waiting for a background task
220
226
  if (name === 'TaskOutput' && input.block === true) {
221
227
  const taskTimeout = input.timeout || 600000; // default 10min
222
- blockingTimeout = Math.max(heartbeatTimeout, taskTimeout + 60000); // task timeout + 1min grace
228
+ blockingTimeout = Math.max(itemHeartbeat, taskTimeout + 60000); // task timeout + 1min grace
223
229
  isBlocking = true;
224
230
  blockingTool = 'TaskOutput';
225
231
  }
@@ -227,13 +233,13 @@ function checkTimeouts(config) {
227
233
  if (name === 'Bash') {
228
234
  // Use explicit timeout if set, otherwise match Claude Code's actual Bash default (120s)
229
235
  const bashTimeout = input.timeout || 120000;
230
- blockingTimeout = Math.max(heartbeatTimeout, bashTimeout + 60000);
236
+ blockingTimeout = Math.max(itemHeartbeat, bashTimeout + 60000);
231
237
  isBlocking = true;
232
238
  blockingTool = 'Bash';
233
239
  }
234
240
  // Agent (subagent) tool call — parent waits silently for child to complete
235
241
  if (name === 'Agent') {
236
- blockingTimeout = Math.max(heartbeatTimeout, 1800000); // 30min for subagents
242
+ blockingTimeout = Math.max(itemHeartbeat, 1800000); // 30min for subagents
237
243
  isBlocking = true;
238
244
  blockingTool = 'Agent';
239
245
  }
@@ -259,7 +265,7 @@ function checkTimeouts(config) {
259
265
  blockingAnnotations.set(item.id, null);
260
266
  }
261
267
 
262
- const effectiveTimeout = isBlocking ? blockingTimeout : heartbeatTimeout;
268
+ const effectiveTimeout = isBlocking ? blockingTimeout : itemHeartbeat;
263
269
 
264
270
  // Skip recently-steered agents — they're being killed and re-spawned
265
271
  const procInfo = activeProcesses.get(item.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.729",
3
+ "version": "0.1.730",
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"