@yemi33/minions 0.1.669 → 0.1.671
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 +5 -1
- package/dashboard/js/render-agents.js +2 -0
- package/engine/queries.js +28 -0
- package/engine/timeout.js +35 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.671 (2026-04-09)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
- populate _artifacts on work items for detail modal
|
|
4
7
|
|
|
5
8
|
### Fixes
|
|
9
|
+
- surface blocking tool call state in dashboard (closes #619) (#634)
|
|
6
10
|
- interrupt blocking tool calls immediately on steering (closes #627) (#632)
|
|
7
11
|
- reduce Bash blocking grace and errored task dedup window (closes #593) (#630)
|
|
8
12
|
|
|
@@ -11,6 +11,7 @@ function renderAgents(agents) {
|
|
|
11
11
|
</div>
|
|
12
12
|
<div class="agent-role">${escHtml(a.role)}</div>
|
|
13
13
|
<div class="agent-action" title="${escHtml(a.lastAction)}">${escHtml(a.lastAction)}</div>
|
|
14
|
+
${a._blockingToolCall ? `<div style="margin-top:4px;padding:4px 8px;background:rgba(130,160,210,0.13);border:1px solid rgba(130,160,210,0.3);border-radius:4px;font-size:10px;color:var(--muted)">⏳ Blocking tool call (${escHtml(a._blockingToolCall.tool)}) — silent ${Math.round(a._blockingToolCall.silentMs/60000)}min, timeout in ${Math.round(a._blockingToolCall.remainingMs/60000)}min</div>` : ''}
|
|
14
15
|
${a._warning ? `<div style="margin-top:4px;padding:4px 8px;background:rgba(210,153,34,0.15);border:1px solid rgba(210,153,34,0.3);border-radius:4px;font-size:10px;color:var(--yellow)">⚠ ${escHtml(a._warning)}</div>` : ''}
|
|
15
16
|
${a._permissionMode && a._permissionMode !== 'bypassPermissions' && !a._warning ? `<div style="margin-top:4px;font-size:9px;color:var(--muted)">Permission mode: ${escHtml(a._permissionMode)}</div>` : ''}
|
|
16
17
|
${a.resultSummary ? `<div class="agent-result" title="${escHtml(a.resultSummary)}">${renderMd(a.resultSummary.slice(0, 200))}${a.resultSummary.length > 200 ? '...' : ''}</div>` : ''}
|
|
@@ -31,6 +32,7 @@ async function openAgentDetail(id) {
|
|
|
31
32
|
document.getElementById('detail-status-line').innerHTML =
|
|
32
33
|
'<span class="status-badge ' + badgeClass + '">' + agent.status.toUpperCase() + '</span> ' +
|
|
33
34
|
'<span style="color:var(--muted)">' + escHtml(agent.lastAction) + '</span>' +
|
|
35
|
+
(agent._blockingToolCall ? '<div style="margin-top:4px;padding:4px 8px;background:rgba(130,160,210,0.13);border:1px solid rgba(130,160,210,0.3);border-radius:4px;font-size:11px;color:var(--muted)">⏳ Blocking tool call (' + escHtml(agent._blockingToolCall.tool) + ') — silent ' + Math.round(agent._blockingToolCall.silentMs/60000) + 'min, timeout in ' + Math.round(agent._blockingToolCall.remainingMs/60000) + 'min</div>' : '') +
|
|
34
36
|
(agent.resultSummary ? '<div style="margin-top:4px;font-size:11px;color:var(--text);line-height:1.4">' + renderMd(agent.resultSummary.slice(0, 300)) + '</div>' : '');
|
|
35
37
|
|
|
36
38
|
// Show panel immediately with loading state — don't wait for API
|
package/engine/queries.js
CHANGED
|
@@ -216,6 +216,10 @@ function getAgentStatus(agentId) {
|
|
|
216
216
|
branch: active.meta?.branch || '',
|
|
217
217
|
started_at: active.started_at || active.created_at || null,
|
|
218
218
|
};
|
|
219
|
+
// Surface blocking tool call state from dispatch annotation (set by timeout.js)
|
|
220
|
+
if (active._blockingToolCall) {
|
|
221
|
+
result._blockingToolCall = active._blockingToolCall;
|
|
222
|
+
}
|
|
219
223
|
// Detect permission-waiting: read only head+tail of live-output.log (max 2KB total)
|
|
220
224
|
try {
|
|
221
225
|
const liveLogPath = path.join(AGENTS_DIR, agentId, 'live-output.log');
|
|
@@ -330,6 +334,7 @@ function getAgents(config) {
|
|
|
330
334
|
...a, status: s.status, lastAction,
|
|
331
335
|
currentTask: (s.task || '').slice(0, 200),
|
|
332
336
|
resultSummary: (s.resultSummary || '').slice(0, 500),
|
|
337
|
+
_blockingToolCall: s._blockingToolCall || null,
|
|
333
338
|
_warning: s._warning || null,
|
|
334
339
|
_permissionMode: s._permissionMode || null,
|
|
335
340
|
chartered, inboxCount: inboxFiles.length
|
|
@@ -701,6 +706,29 @@ function getWorkItems(config) {
|
|
|
701
706
|
}
|
|
702
707
|
}
|
|
703
708
|
|
|
709
|
+
// Populate _artifacts for the work item detail modal
|
|
710
|
+
for (const item of allItems) {
|
|
711
|
+
const arts = {};
|
|
712
|
+
const agentId = item.dispatched_to || item.agent;
|
|
713
|
+
if (agentId) {
|
|
714
|
+
// Output log — find most recent matching dispatch output
|
|
715
|
+
const agentDir = path.join(MINIONS_DIR, 'agents', agentId);
|
|
716
|
+
try {
|
|
717
|
+
const files = safeReadDir(agentDir).filter(f => f.startsWith('output-') && f.includes(item.id?.slice(-8) || '___') && f.endsWith('.log'));
|
|
718
|
+
if (files.length > 0) arts.outputLog = agentId + '/' + files[files.length - 1];
|
|
719
|
+
} catch {}
|
|
720
|
+
// Inbox notes created by this agent for this task
|
|
721
|
+
try {
|
|
722
|
+
const inboxFiles = safeReadDir(INBOX_DIR).filter(f => f.includes(agentId) && (f.includes(item.id || '___') || f.includes(item.id?.slice(-8) || '___')));
|
|
723
|
+
if (inboxFiles.length > 0) arts.notes = inboxFiles;
|
|
724
|
+
} catch {}
|
|
725
|
+
}
|
|
726
|
+
if (item.branch) arts.branch = item.branch;
|
|
727
|
+
if (item.sourcePlan) arts.sourcePlan = item.sourcePlan;
|
|
728
|
+
if (item._pr) arts.pr = item._pr;
|
|
729
|
+
if (Object.keys(arts).length > 0) item._artifacts = arts;
|
|
730
|
+
}
|
|
731
|
+
|
|
704
732
|
const statusOrder = {
|
|
705
733
|
pending: 0,
|
|
706
734
|
queued: 0,
|
package/engine/timeout.js
CHANGED
|
@@ -146,6 +146,7 @@ function checkTimeouts(config) {
|
|
|
146
146
|
// Uses live-output.log mtime as heartbeat. If no output for heartbeatTimeout, agent is dead.
|
|
147
147
|
const dispatchData = getDispatch();
|
|
148
148
|
const deadItems = [];
|
|
149
|
+
const blockingAnnotations = new Map(); // id → { tool, silentMs, remainingMs } or null (clear)
|
|
149
150
|
|
|
150
151
|
for (const item of (dispatchData.active || [])) {
|
|
151
152
|
if (!item.agent) continue;
|
|
@@ -198,6 +199,7 @@ function checkTimeouts(config) {
|
|
|
198
199
|
// Check for BOTH tracked and untracked processes (orphan case after engine restart)
|
|
199
200
|
let isBlocking = false;
|
|
200
201
|
let blockingTimeout = heartbeatTimeout;
|
|
202
|
+
let blockingTool = '';
|
|
201
203
|
if (silentMs > heartbeatTimeout) {
|
|
202
204
|
try {
|
|
203
205
|
const liveLog = safeRead(liveLogPath);
|
|
@@ -218,6 +220,7 @@ function checkTimeouts(config) {
|
|
|
218
220
|
const taskTimeout = input.timeout || 600000; // default 10min
|
|
219
221
|
blockingTimeout = Math.max(heartbeatTimeout, taskTimeout + 60000); // task timeout + 1min grace
|
|
220
222
|
isBlocking = true;
|
|
223
|
+
blockingTool = 'TaskOutput';
|
|
221
224
|
}
|
|
222
225
|
// Bash tool call — may be running a long build/install with no stdout
|
|
223
226
|
if (name === 'Bash') {
|
|
@@ -225,21 +228,35 @@ function checkTimeouts(config) {
|
|
|
225
228
|
const bashTimeout = input.timeout || 120000;
|
|
226
229
|
blockingTimeout = Math.max(heartbeatTimeout, bashTimeout + 60000);
|
|
227
230
|
isBlocking = true;
|
|
231
|
+
blockingTool = 'Bash';
|
|
228
232
|
}
|
|
229
233
|
// Agent (subagent) tool call — parent waits silently for child to complete
|
|
230
234
|
if (name === 'Agent') {
|
|
231
235
|
blockingTimeout = Math.max(heartbeatTimeout, 1800000); // 30min for subagents
|
|
232
236
|
isBlocking = true;
|
|
237
|
+
blockingTool = 'Agent';
|
|
233
238
|
}
|
|
234
239
|
break; // only check the most recent tool_use
|
|
235
240
|
} catch { /* JSON parse — line may not be valid JSON */ }
|
|
236
241
|
}
|
|
237
242
|
if (isBlocking) {
|
|
238
|
-
|
|
243
|
+
// Only log on transition — avoid spamming every tick while blocking persists
|
|
244
|
+
if (!item._blockingToolCall) {
|
|
245
|
+
log('info', `Agent ${item.agent} (${item.id}) is in a blocking tool call (${blockingTool}) — extended timeout to ${Math.round(blockingTimeout / 1000)}s (silent for ${silentSec}s)`, { event: 'blocking_tool_call_detected' });
|
|
246
|
+
}
|
|
247
|
+
blockingAnnotations.set(item.id, {
|
|
248
|
+
tool: blockingTool,
|
|
249
|
+
silentMs,
|
|
250
|
+
remainingMs: Math.max(0, blockingTimeout - silentMs),
|
|
251
|
+
});
|
|
239
252
|
}
|
|
240
253
|
}
|
|
241
254
|
} catch (e) { log('warn', 'blocking tool detection: ' + e.message); }
|
|
242
255
|
}
|
|
256
|
+
// Agent recovered from blocking state — clear annotation
|
|
257
|
+
if (!isBlocking && item._blockingToolCall) {
|
|
258
|
+
blockingAnnotations.set(item.id, null);
|
|
259
|
+
}
|
|
243
260
|
|
|
244
261
|
const effectiveTimeout = isBlocking ? blockingTimeout : heartbeatTimeout;
|
|
245
262
|
|
|
@@ -273,6 +290,23 @@ function checkTimeouts(config) {
|
|
|
273
290
|
completeDispatch(item.id, DISPATCH_RESULT.ERROR, reason);
|
|
274
291
|
}
|
|
275
292
|
|
|
293
|
+
// Batch-write blocking tool call annotations to dispatch entries.
|
|
294
|
+
// This surfaces blocking state via GET /api/status → dashboard badges.
|
|
295
|
+
if (blockingAnnotations.size > 0) {
|
|
296
|
+
const { mutateDispatch: mutateDispatchFn } = dispatch();
|
|
297
|
+
mutateDispatchFn((dp) => {
|
|
298
|
+
for (const activeItem of dp.active) {
|
|
299
|
+
if (!blockingAnnotations.has(activeItem.id)) continue;
|
|
300
|
+
const ann = blockingAnnotations.get(activeItem.id);
|
|
301
|
+
if (ann) {
|
|
302
|
+
activeItem._blockingToolCall = ann;
|
|
303
|
+
} else {
|
|
304
|
+
delete activeItem._blockingToolCall;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
276
310
|
// Agent status is now derived from dispatch.json at read time (getAgentStatus).
|
|
277
311
|
// No reconcile sweep needed — dispatch IS the source of truth.
|
|
278
312
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.671",
|
|
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"
|