@yemi33/minions 0.1.743 → 0.1.744
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 +2 -1
- package/engine/lifecycle.js +1 -1
- package/engine/spawn-agent.js +4 -0
- package/engine/timeout.js +31 -9
- package/engine.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.744 (2026-04-09)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
6
|
- add prNumber field to pull-requests.json records (#711)
|
|
7
7
|
|
|
8
8
|
### Fixes
|
|
9
|
+
- handle max_turns exit in dispatch lifecycle cleanup (closes #716) (#735)
|
|
9
10
|
- link plan files as artifacts on plan/plan-to-prd work items
|
|
10
11
|
- escalate failed plan items instead of blocking indefinitely (closes #722) (#733)
|
|
11
12
|
- handle NUL pseudo-file in Windows worktree cleanup (#731)
|
package/engine/lifecycle.js
CHANGED
|
@@ -1560,7 +1560,7 @@ function classifyFailure(code, stdout = '', stderr = '') {
|
|
|
1560
1560
|
}
|
|
1561
1561
|
|
|
1562
1562
|
// Context window / max turns exhausted
|
|
1563
|
-
if (/context window|max.*turns.*reached|token limit|conversation.*too long|context.*length.*exceeded/i.test(combined)) {
|
|
1563
|
+
if (/context window|max.*turns.*reached|error_max_turns|terminal_reason.*max_turns|token limit|conversation.*too long|context.*length.*exceeded/i.test(combined)) {
|
|
1564
1564
|
return FAILURE_CLASS.OUT_OF_CONTEXT;
|
|
1565
1565
|
}
|
|
1566
1566
|
|
package/engine/spawn-agent.js
CHANGED
|
@@ -176,6 +176,10 @@ proc.stdout.once('data', () => { gotFirstOutput = true; clearTimeout(startupTime
|
|
|
176
176
|
|
|
177
177
|
proc.on('close', (code) => {
|
|
178
178
|
clearTimeout(startupTimer);
|
|
179
|
+
// Write process-exit sentinel to stdout so the engine can detect completion (#716).
|
|
180
|
+
// This is a backup for cases where Claude CLI crashes without writing a result line.
|
|
181
|
+
// process.stdout.write is synchronous for pipes, so it will be captured by the parent.
|
|
182
|
+
try { process.stdout.write(`\n[process-exit] code=${code}\n`); } catch { /* stdout may be closed */ }
|
|
179
183
|
fs.appendFileSync(debugPath, `EXIT: code=${code}\nSTDERR: ${stderrBuf.slice(0, 500)}\n`);
|
|
180
184
|
process.exit(code || 0);
|
|
181
185
|
});
|
package/engine/timeout.js
CHANGED
|
@@ -177,27 +177,40 @@ function checkTimeouts(config) {
|
|
|
177
177
|
const silentMs = Date.now() - lastActivity;
|
|
178
178
|
const silentSec = Math.round(silentMs / 1000);
|
|
179
179
|
|
|
180
|
-
// Check if the agent actually completed (result event in live output)
|
|
181
|
-
//
|
|
180
|
+
// Check if the agent actually completed (result event in live output).
|
|
181
|
+
// Read the tail of the log (last 64KB) for efficiency — result JSON is always near the end.
|
|
182
|
+
// No time cap: a stuck dispatch that produced a result must always be detected (#716).
|
|
182
183
|
let completedViaOutput = false;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
try {
|
|
185
|
+
let liveLog;
|
|
186
|
+
try {
|
|
187
|
+
const fd = fs.openSync(liveLogPath, 'r');
|
|
188
|
+
const stat = fs.fstatSync(fd);
|
|
189
|
+
const TAIL_SIZE = 65536; // 64KB
|
|
190
|
+
const tailSize = Math.min(stat.size, TAIL_SIZE);
|
|
191
|
+
const buf = Buffer.alloc(tailSize);
|
|
192
|
+
fs.readSync(fd, buf, 0, tailSize, Math.max(0, stat.size - tailSize));
|
|
193
|
+
fs.closeSync(fd);
|
|
194
|
+
liveLog = buf.toString('utf8');
|
|
195
|
+
} catch { /* ENOENT or read failure — liveLog stays undefined */ }
|
|
196
|
+
if (liveLog && (liveLog.includes('"type":"result"') || liveLog.includes('[process-exit]'))) {
|
|
186
197
|
completedViaOutput = true;
|
|
187
198
|
const isSuccess = liveLog.includes('"subtype":"success"');
|
|
188
199
|
log('info', `Agent ${item.agent} (${item.id}) completed via output detection (${isSuccess ? 'success' : 'error'})`);
|
|
189
200
|
|
|
190
|
-
// Extract output text for the output.log
|
|
201
|
+
// Extract output text for the output.log — read full file for complete parsing
|
|
191
202
|
const outputLogPath = path.join(AGENTS_DIR, item.agent, 'output.log');
|
|
192
203
|
try {
|
|
193
|
-
const
|
|
204
|
+
const fullLog = safeRead(liveLogPath) || liveLog;
|
|
205
|
+
const { text } = shared.parseStreamJsonOutput(fullLog);
|
|
194
206
|
safeWrite(outputLogPath, `# Output for dispatch ${item.id}\n# Exit code: ${isSuccess ? 0 : 1}\n# Completed: ${ts()}\n# Detected via output scan\n\n## Result\n${text || '(no text)'}\n`);
|
|
195
207
|
} catch (e) { log('warn', 'parse output result: ' + e.message); }
|
|
196
208
|
|
|
197
209
|
completeDispatch(item.id, isSuccess ? DISPATCH_RESULT.SUCCESS : DISPATCH_RESULT.ERROR, 'Completed (detected from output)');
|
|
198
210
|
|
|
199
211
|
// Run post-completion hooks via shared helper (async — fire and forget in timeout context)
|
|
200
|
-
|
|
212
|
+
const fullLogForHooks = safeRead(liveLogPath) || liveLog;
|
|
213
|
+
runPostCompletionHooks(item, item.agent, isSuccess ? 0 : 1, fullLogForHooks, config).catch(e => log('warn', 'post-completion hooks: ' + e.message));
|
|
201
214
|
|
|
202
215
|
if (hasProcess) {
|
|
203
216
|
shared.killImmediate(activeProcesses.get(item.id)?.proc);
|
|
@@ -213,6 +226,8 @@ function checkTimeouts(config) {
|
|
|
213
226
|
// Check if agent is in a blocking tool call (TaskOutput block:true, Bash with long timeout, etc.)
|
|
214
227
|
// These tools produce no stdout for extended periods — don't kill them prematurely
|
|
215
228
|
// Check for BOTH tracked and untracked processes (orphan case after engine restart)
|
|
229
|
+
// Skip if agent already completed — blocking tool detection on stale tool calls
|
|
230
|
+
// would extend the timeout indefinitely for dead agents (#716).
|
|
216
231
|
let isBlocking = false;
|
|
217
232
|
let blockingTimeout = itemHeartbeat;
|
|
218
233
|
let blockingTool = '';
|
|
@@ -220,6 +235,12 @@ function checkTimeouts(config) {
|
|
|
220
235
|
try {
|
|
221
236
|
const liveLog = safeRead(liveLogPath);
|
|
222
237
|
if (liveLog) {
|
|
238
|
+
// If the output contains a result event or process-exit sentinel, the agent is done.
|
|
239
|
+
// Don't extend timeout for stale blocking tool calls from before the result (#716).
|
|
240
|
+
if (liveLog.includes('"type":"result"') || liveLog.includes('[process-exit]')) {
|
|
241
|
+
// Agent completed but close event didn't fire — let orphan/hung detection handle it.
|
|
242
|
+
// Don't set isBlocking — use base heartbeat timeout.
|
|
243
|
+
} else {
|
|
223
244
|
// Find the last tool_use call in the output — check if it's a known blocking tool
|
|
224
245
|
const lines = liveLog.split('\n');
|
|
225
246
|
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 30); i--) {
|
|
@@ -266,7 +287,8 @@ function checkTimeouts(config) {
|
|
|
266
287
|
remainingMs: Math.max(0, blockingTimeout - silentMs),
|
|
267
288
|
});
|
|
268
289
|
}
|
|
269
|
-
|
|
290
|
+
} // close else
|
|
291
|
+
} // close if (liveLog)
|
|
270
292
|
} catch (e) { log('warn', 'blocking tool detection: ' + e.message); }
|
|
271
293
|
}
|
|
272
294
|
// Agent recovered from blocking state — clear annotation
|
package/engine.js
CHANGED
|
@@ -723,7 +723,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
723
723
|
}
|
|
724
724
|
|
|
725
725
|
// Re-attach to existing tracking
|
|
726
|
-
activeProcesses.set(id, { proc: resumeProc, agentId, startedAt: procInfo.startedAt, sessionId: steerSessionId, _steeringAt: Date.now() });
|
|
726
|
+
activeProcesses.set(id, { proc: resumeProc, agentId, startedAt: procInfo.startedAt, sessionId: steerSessionId, _steeringAt: Date.now(), lastRealOutputAt: Date.now() });
|
|
727
727
|
|
|
728
728
|
// Reset output buffers so post-completion parsing only sees the resumed session
|
|
729
729
|
stdout = '';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.744",
|
|
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"
|