@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 +3 -1
- package/dashboard/js/render-work-items.js +3 -2
- package/engine/shared.js +2 -1
- package/engine/timeout.js +12 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
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 =
|
|
208
|
+
let blockingTimeout = itemHeartbeat;
|
|
203
209
|
let blockingTool = '';
|
|
204
|
-
if (silentMs >
|
|
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(
|
|
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(
|
|
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(
|
|
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 :
|
|
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.
|
|
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"
|