@yemi33/minions 0.1.1048 → 0.1.1049
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 +4 -1
- package/engine/timeout.js +25 -7
- package/engine.js +21 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/engine/timeout.js
CHANGED
|
@@ -308,15 +308,33 @@ function checkTimeouts(config) {
|
|
|
308
308
|
const procInfo = activeProcesses.get(item.id);
|
|
309
309
|
if (procInfo?._steeringAt && Date.now() - procInfo._steeringAt < 60000) continue;
|
|
310
310
|
|
|
311
|
-
// Capture live-output.log file state for orphan/hung diagnostics
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
// logExists=
|
|
315
|
-
// logExists=true
|
|
316
|
-
|
|
311
|
+
// Capture live-output.log file state for orphan/hung diagnostics
|
|
312
|
+
// (#W-mo248lkjwgsu original, #W-mo25loq8kjer pid annotation).
|
|
313
|
+
// Four distinguishable failure modes:
|
|
314
|
+
// logExists=false → spawn call itself threw, no log ever written
|
|
315
|
+
// logExists=true pidPresent=false → engine stub written but spawn died before emitting pid line
|
|
316
|
+
// logExists=true pidPresent=true silent → process spawned (pid recorded) but never produced stdout
|
|
317
|
+
// logExists=true pidPresent=true size>pid → genuine hang (process wrote output then stopped)
|
|
318
|
+
//
|
|
319
|
+
// The pid line `[<iso>] pid: <N>` is stamped by engine.js immediately after runFile() returns.
|
|
320
|
+
// Its presence → the child process was actually spawned; absence → spawn itself failed or the
|
|
321
|
+
// appendFileSync on the pid line threw (rare).
|
|
322
|
+
let _logState = 'logExists=false logSize=0 pidPresent=false';
|
|
317
323
|
try {
|
|
318
324
|
const lst = fs.statSync(liveLogPath);
|
|
319
|
-
|
|
325
|
+
// Read only the head (4KB) — pid line is written right after the stub, always near the top.
|
|
326
|
+
// Avoids loading the full log just for a diagnostic annotation.
|
|
327
|
+
let pidPresent = false;
|
|
328
|
+
try {
|
|
329
|
+
const fd = fs.openSync(liveLogPath, 'r');
|
|
330
|
+
const headSize = Math.min(lst.size, 4096);
|
|
331
|
+
const headBuf = Buffer.alloc(headSize);
|
|
332
|
+
fs.readSync(fd, headBuf, 0, headSize, 0);
|
|
333
|
+
fs.closeSync(fd);
|
|
334
|
+
// Match `] pid: <digits>` — agnostic to ISO timestamp format at the start.
|
|
335
|
+
pidPresent = /\]\s+pid:\s+\d+/.test(headBuf.toString('utf8'));
|
|
336
|
+
} catch { /* read failure — pidPresent stays false */ }
|
|
337
|
+
_logState = `logExists=true logSize=${lst.size} pidPresent=${pidPresent}`;
|
|
320
338
|
} catch { /* ENOENT — keep default */ }
|
|
321
339
|
|
|
322
340
|
if (!hasProcess && silentMs > effectiveTimeout && (Date.now() > engineRestartGraceUntil || engineRestartGraceExempt?.has(item.id))) {
|
package/engine.js
CHANGED
|
@@ -916,6 +916,23 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
916
916
|
throw spawnErr;
|
|
917
917
|
}
|
|
918
918
|
|
|
919
|
+
// Seed realActivityMap and stamp PID immediately — BEFORE any handlers or timers (#W-mo25loq8kjer).
|
|
920
|
+
// Why NOW, not later in the function:
|
|
921
|
+
// 1. Heartbeat clock anchoring. timeout.js uses realActivityMap as the last-activity timestamp for
|
|
922
|
+
// tracked processes; when the map has no entry, it falls back to item.started_at (dispatch time,
|
|
923
|
+
// which is 20-60s before actual spawn for write tasks doing worktree setup). Read-only tasks
|
|
924
|
+
// that produce no stdout for minutes (explore, security audit, large scans) were hitting
|
|
925
|
+
// heartbeatTimeout prematurely — clock had already been running since dispatch.
|
|
926
|
+
// 2. Error-handler race. The `proc.on('error', ...)` handler below calls realActivityMap.delete(id)
|
|
927
|
+
// on synchronous spawn failures. Seeding before registering handlers ensures delete sees a value
|
|
928
|
+
// to clear rather than leaving an absent-then-absent no-op that downstream code must guard.
|
|
929
|
+
// 3. Orphan diagnostics. The PID line gives timeout.js a deterministic way to tell "spawn died
|
|
930
|
+
// before first write" (stub-only log) from "process started and is hung" (stub + pid line).
|
|
931
|
+
realActivityMap.set(id, Date.now());
|
|
932
|
+
try {
|
|
933
|
+
fs.appendFileSync(liveOutputPath, `[${new Date().toISOString()}] pid: ${proc.pid ?? 'unknown'}\n`);
|
|
934
|
+
} catch { /* log stamp is best-effort — don't block spawn on fs failure */ }
|
|
935
|
+
|
|
919
936
|
const MAX_OUTPUT = 1024 * 1024; // 1MB
|
|
920
937
|
let stdout = '';
|
|
921
938
|
let stderr = '';
|
|
@@ -1292,9 +1309,11 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1292
1309
|
}
|
|
1293
1310
|
}, 3000);
|
|
1294
1311
|
|
|
1295
|
-
// Track process — even if PID isn't available yet (async on Windows)
|
|
1312
|
+
// Track process — even if PID isn't available yet (async on Windows).
|
|
1313
|
+
// realActivityMap was already seeded immediately after runFile() returned (#W-mo25loq8kjer);
|
|
1314
|
+
// don't re-seed here — the stdout/stderr handlers above can already have updated it with
|
|
1315
|
+
// a fresher timestamp, and overwriting would clobber the real "last activity" signal.
|
|
1296
1316
|
activeProcesses.set(id, { proc, agentId, startedAt, sessionId: cachedSessionId });
|
|
1297
|
-
realActivityMap.set(id, Date.now()); // Initialize real activity at spawn time
|
|
1298
1317
|
|
|
1299
1318
|
updateAgentStatus(id, AGENT_STATUS.RUNNING, `Process spawned for ${agentId}`);
|
|
1300
1319
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1049",
|
|
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"
|