@yemi33/minions 0.1.2308 → 0.1.2309
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/bin/minions.js +22 -2
- package/docs/README.md +1 -0
- package/docs/diagnostics-crash-reports.md +132 -0
- package/engine/cleanup.js +13 -0
- package/engine/shared.js +86 -0
- package/engine/supervisor.js +13 -0
- package/package.json +1 -1
package/bin/minions.js
CHANGED
|
@@ -391,6 +391,19 @@ function _resolveRestartHealthTimeoutMs() {
|
|
|
391
391
|
return Math.min(300000, Math.max(15000, resolved));
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Read config.json best-effort for callers that spawn engine.js before any
|
|
396
|
+
* config is loaded into memory. Mirrors _resolveRestartHealthTimeoutMs's
|
|
397
|
+
* inline read.
|
|
398
|
+
*/
|
|
399
|
+
function _readConfigJsonSafe() {
|
|
400
|
+
const home = process.env.MINIONS_HOME || (typeof MINIONS_HOME !== 'undefined' ? MINIONS_HOME : null);
|
|
401
|
+
try {
|
|
402
|
+
if (home) return JSON.parse(fs.readFileSync(path.join(home, 'config.json'), 'utf8'));
|
|
403
|
+
} catch { /* config may not exist yet */ }
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
|
|
394
407
|
/**
|
|
395
408
|
* Spawn engine + dashboard + supervisor, verify health, optionally open browser.
|
|
396
409
|
* Shared by `minions start` and `minions restart` — restart layers a kill phase
|
|
@@ -401,8 +414,14 @@ function _resolveRestartHealthTimeoutMs() {
|
|
|
401
414
|
function spawnFullStackAndVerify({ rest, forceOpen, dashWasUp, restartStartMs }) {
|
|
402
415
|
const engineOut = _openStdioLog('engine-stdio.log');
|
|
403
416
|
const engineErr = _openStdioLog('engine-stdio.log');
|
|
417
|
+
// W-mr2c4i8m0004da94: enable Node's crash-diagnostics report for the
|
|
418
|
+
// engine.js process so a future silent death (the class investigated in
|
|
419
|
+
// W-mr2azk6i) leaves a JSON report under engine/diagnostics/crash-reports
|
|
420
|
+
// instead of vanishing with no trace.
|
|
421
|
+
const crashDiagEnv = shared.getEngineCrashDiagnosticsEnv(_readConfigJsonSafe());
|
|
404
422
|
const engineProc = spawn(process.execPath, [..._sqliteSpawnFlags(), path.join(MINIONS_HOME, 'engine.js'), 'start', ...rest], {
|
|
405
|
-
cwd: MINIONS_HOME, stdio: ['ignore', engineOut, engineErr], detached: true, windowsHide: true
|
|
423
|
+
cwd: MINIONS_HOME, stdio: ['ignore', engineOut, engineErr], detached: true, windowsHide: true,
|
|
424
|
+
env: { ...process.env, ...crashDiagEnv }
|
|
406
425
|
});
|
|
407
426
|
engineProc.unref();
|
|
408
427
|
console.log(`\n Engine started (PID: ${engineProc.pid})`);
|
|
@@ -934,7 +953,8 @@ function init() {
|
|
|
934
953
|
? `\n Upgrade complete (${pkgVersion}). Restarting engine and dashboard...\n`
|
|
935
954
|
: '\n Starting engine and dashboard...\n');
|
|
936
955
|
const engineProc = spawn(process.execPath, [..._sqliteSpawnFlags(), path.join(MINIONS_HOME, 'engine.js'), 'start'], {
|
|
937
|
-
cwd: MINIONS_HOME, stdio: 'ignore', detached: true, windowsHide: true
|
|
956
|
+
cwd: MINIONS_HOME, stdio: 'ignore', detached: true, windowsHide: true,
|
|
957
|
+
env: { ...process.env, ...shared.getEngineCrashDiagnosticsEnv(_readConfigJsonSafe()) }
|
|
938
958
|
});
|
|
939
959
|
engineProc.unref();
|
|
940
960
|
console.log(` Engine started (PID: ${engineProc.pid})`);
|
package/docs/README.md
CHANGED
|
@@ -61,6 +61,7 @@ Operational runbooks for engine operators and fleet maintainers.
|
|
|
61
61
|
- [notes.md](notes.md) — Consolidated team knowledge: patterns, conventions, bugs, and build findings accumulated from agent inbox notes. Read by the engine and injected into agent prompts as shared context.
|
|
62
62
|
- [auto-discovery.md](auto-discovery.md) — Auto-discovery and execution pipeline: the per-tick orchestration loop and the four work-discovery sources.
|
|
63
63
|
- [diagnostics-memory.md](diagnostics-memory.md) — Operator runbook for the in-process memory + perf observability surface: `/api/diagnostics/memory[/history]`, `/api/diagnostics/heap-snapshot` guard-token capture, `MEMORY_BASELINE` log emissions, `--cpu-prof`/`--heap-prof` capture, and the `test/perf/soak.test.js` heap-growth regression gate.
|
|
64
|
+
- [diagnostics-crash-reports.md](diagnostics-crash-reports.md) — Proactive Node crash-diagnostics reports (`--report-on-fatalerror --report-on-signal --diagnostic-dir=...`) for the engine.js process on Windows: where reports land, config/opt-out, and retention.
|
|
64
65
|
- [engine-restart.md](engine-restart.md) — How agents survive an engine restart: state persistence, the 20-minute startup grace period, and orphan reattachment via PID files and `live-output.log`.
|
|
65
66
|
- [human-vs-automated.md](human-vs-automated.md) — Quick reference table of which features humans start, run, decide, and recover, and the two human approval gates.
|
|
66
67
|
- [kb-sweep.md](kb-sweep.md) — Knowledge-base sweep runbook: how `engine/kb-sweep.js` consolidates `notes/inbox/` into `knowledge/` and survives `minions restart`.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Diagnostics: crash reports for the engine.js process (Windows)
|
|
2
|
+
|
|
3
|
+
Operator runbook for the proactive crash-diagnostics feature shipped for
|
|
4
|
+
`W-mr2c4i8m0004da94`. Follow-up from `W-mr2azk6i` (2026-07-01 engine.js
|
|
5
|
+
crash-loop investigation): a run of 4 silent `engine.js` deaths left no
|
|
6
|
+
exception/stack trace in `engine-stdio.log`, no Windows Event Viewer
|
|
7
|
+
entries (Application/System logs were empty for the window), and no crash
|
|
8
|
+
dumps in `C:\Windows\Minidump` or `%LOCALAPPDATA%\CrashDumps`. The box had
|
|
9
|
+
no crash-event logging wired up at all, so a genuine native/access-violation
|
|
10
|
+
crash of `node.exe` would have vanished without a trace.
|
|
11
|
+
|
|
12
|
+
## What this does
|
|
13
|
+
|
|
14
|
+
Every spawn of `engine.js` — via `minions start`/`restart` (`bin/minions.js`)
|
|
15
|
+
**and** the supervisor's respawn-on-death path (`engine/supervisor.js`) —
|
|
16
|
+
now gets Node's own diagnostic-report flags folded into `NODE_OPTIONS`:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
--report-on-fatalerror --report-on-signal --diagnostic-dir=<MINIONS_HOME>/engine/diagnostics/crash-reports
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This is a zero-dependency, built-in Node feature (no Sysinternals ProcDump
|
|
23
|
+
or other external tool needed):
|
|
24
|
+
|
|
25
|
+
- **`--report-on-fatalerror`** — writes a JSON diagnostic report the moment
|
|
26
|
+
the process hits an unrecoverable V8 error: out-of-memory, JS stack
|
|
27
|
+
overflow, or a native-addon crash. This is exactly the class of death a
|
|
28
|
+
silent `node.exe` disappearance on Windows would otherwise hide.
|
|
29
|
+
- **`--report-on-signal`** — lets an operator request a report on demand by
|
|
30
|
+
sending the configured signal to the running engine PID: `SIGUSR2` on
|
|
31
|
+
POSIX, `SIGBREAK` on Windows (Node's default `--report-signal`). Useful to
|
|
32
|
+
capture a report while a hang is in progress, before deciding to kill it.
|
|
33
|
+
- **`--diagnostic-dir=...`** — where both of the above land.
|
|
34
|
+
|
|
35
|
+
Reports are plain JSON (`report.<timestamp>.<pid>.<seq>.json`) containing
|
|
36
|
+
the JS/native stack, loaded modules, resource usage, and libuv handle
|
|
37
|
+
summary at the moment of the event — see [Node's diagnostic report
|
|
38
|
+
docs](https://nodejs.org/api/report.html) for the full schema.
|
|
39
|
+
|
|
40
|
+
## Where reports land
|
|
41
|
+
|
|
42
|
+
`<MINIONS_HOME>/engine/diagnostics/crash-reports/` — a sibling of the
|
|
43
|
+
existing `engine/diagnostics/` directory used for heap snapshots and
|
|
44
|
+
CPU/heap profiles (see [docs/diagnostics-memory.md](diagnostics-memory.md)
|
|
45
|
+
§3–4), in its own subfolder so retention sweeps don't collide. The directory
|
|
46
|
+
is gitignored and created on first spawn if missing
|
|
47
|
+
(`getEngineCrashDiagnosticsEnv`, [`engine/shared.js`](../engine/shared.js)).
|
|
48
|
+
|
|
49
|
+
## Config + opt-out
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"engine": {
|
|
54
|
+
"crashDiagnostics": {
|
|
55
|
+
"enabled": true,
|
|
56
|
+
"retainCount": 20
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- `enabled` (default `true`) — set `false` to disable the NODE_OPTIONS
|
|
63
|
+
injection entirely (falls back to the pre-existing no-diagnostics
|
|
64
|
+
behavior).
|
|
65
|
+
- `retainCount` (default `20`) — how many `report.*.json` files are kept
|
|
66
|
+
before the oldest are pruned. Restart the engine (`minions restart`)
|
|
67
|
+
after editing `enabled`, since it only takes effect at the next spawn.
|
|
68
|
+
|
|
69
|
+
Both live in `ENGINE_DEFAULTS.crashDiagnostics`
|
|
70
|
+
([`engine/shared.js`](../engine/shared.js)).
|
|
71
|
+
|
|
72
|
+
## Retention / cleanup
|
|
73
|
+
|
|
74
|
+
Reports are pruned by `pruneCrashDiagnosticsReports` (`engine/shared.js`),
|
|
75
|
+
called from `engine/cleanup.js#runCleanup` — the same periodic cleanup pass
|
|
76
|
+
that already runs every `ENGINE_DEFAULTS.cleanupEvery` ticks (default 60
|
|
77
|
+
ticks ≈ 10 min at the default 10 s tick interval). It keeps the newest
|
|
78
|
+
`retainCount` files by mtime and deletes the rest, mirroring the existing
|
|
79
|
+
heap-snapshot retention pattern in `dashboard.js#_heapSnapshotPrune`. Pruning
|
|
80
|
+
is best-effort: a missing directory or a transient Windows file lock is
|
|
81
|
+
swallowed and retried on the next cleanup pass.
|
|
82
|
+
|
|
83
|
+
## Idempotency across the CLI → supervisor respawn chain
|
|
84
|
+
|
|
85
|
+
Both `bin/minions.js` (initial spawn) and `engine/supervisor.js` (respawn on
|
|
86
|
+
death) call the same `getEngineCrashDiagnosticsEnv(config)` helper. It checks
|
|
87
|
+
whether the caller's `NODE_OPTIONS` already contains `--diagnostic-dir=`
|
|
88
|
+
(which a spawned child inherits from its parent's env) and returns `{}`
|
|
89
|
+
unchanged if so — so a CLI-spawned engine that later gets respawned by the
|
|
90
|
+
supervisor never accumulates duplicate flags.
|
|
91
|
+
|
|
92
|
+
## Inspecting a report after a crash
|
|
93
|
+
|
|
94
|
+
```powershell
|
|
95
|
+
Get-ChildItem "$env:USERPROFILE\.minions\engine\diagnostics\crash-reports" |
|
|
96
|
+
Sort-Object LastWriteTime -Descending | Select-Object -First 5
|
|
97
|
+
|
|
98
|
+
Get-Content "$env:USERPROFILE\.minions\engine\diagnostics\crash-reports\report.<ts>.<pid>.001.json" |
|
|
99
|
+
ConvertFrom-Json | Select-Object javascriptStack, header
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Key fields to check first: `header.event` (what triggered the report,
|
|
103
|
+
e.g. `"FatalError"` or `"Signal"`), `javascriptStack`, `nativeStack`, and
|
|
104
|
+
`resourceUsage` (memory/CPU at the moment of the event).
|
|
105
|
+
|
|
106
|
+
## Requesting a report on demand (hang investigation)
|
|
107
|
+
|
|
108
|
+
```powershell
|
|
109
|
+
# Find the engine PID from engine/control.json, then:
|
|
110
|
+
# Windows: send SIGBREAK-equivalent via node's report trigger utility, or
|
|
111
|
+
# use Node's process.report.writeReport() interactively if you have a REPL
|
|
112
|
+
# attached. The simplest cross-platform trigger is `process.kill(pid, 'SIGBREAK')`
|
|
113
|
+
# from another Node process on Windows.
|
|
114
|
+
node -e "process.kill(<pid>, 'SIGBREAK')"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
A fresh `report.*.json` should appear in the crash-reports directory within
|
|
118
|
+
a second or two, without killing the process.
|
|
119
|
+
|
|
120
|
+
## Related modules and tests
|
|
121
|
+
|
|
122
|
+
- Module: [`engine/shared.js`](../engine/shared.js) — `CRASH_REPORTS_DIR`,
|
|
123
|
+
`getEngineCrashDiagnosticsEnv`, `pruneCrashDiagnosticsReports`,
|
|
124
|
+
`ENGINE_DEFAULTS.crashDiagnostics`.
|
|
125
|
+
- Spawn wiring: [`bin/minions.js`](../bin/minions.js)
|
|
126
|
+
(`spawnFullStackAndVerify`, the post-install/upgrade auto-start path) and
|
|
127
|
+
[`engine/supervisor.js`](../engine/supervisor.js) (`spawnEngine`).
|
|
128
|
+
- Cleanup wiring: [`engine/cleanup.js`](../engine/cleanup.js) `runCleanup`.
|
|
129
|
+
- Tests: [`test/unit/crash-diagnostics.test.js`](../test/unit/crash-diagnostics.test.js).
|
|
130
|
+
- Related: [docs/diagnostics-memory.md](diagnostics-memory.md) (memory/GC/heap
|
|
131
|
+
observability — the sibling diagnostics surface this doc's directory lives
|
|
132
|
+
next to).
|
package/engine/cleanup.js
CHANGED
|
@@ -1633,6 +1633,19 @@ async function runCleanup(config, verbose = false) {
|
|
|
1633
1633
|
cleaned.knowledgeScratch = reapKnowledgeScratch();
|
|
1634
1634
|
} catch (e) { log('warn', `reapKnowledgeScratch: ${e.message}`); }
|
|
1635
1635
|
|
|
1636
|
+
// 18. Prune old Node crash-diagnostics reports (W-mr2c4i8m0004da94). Node
|
|
1637
|
+
// writes `report.*.json` to CRASH_REPORTS_DIR on a fatal V8 error or
|
|
1638
|
+
// `--report-on-signal` request — see getEngineCrashDiagnosticsEnv. Keep
|
|
1639
|
+
// only the newest config.engine.crashDiagnostics.retainCount (default 20)
|
|
1640
|
+
// so a crash-loop or repeated on-demand reports don't accumulate unbounded.
|
|
1641
|
+
cleaned.crashReports = 0;
|
|
1642
|
+
try {
|
|
1643
|
+
cleaned.crashReports = shared.pruneCrashDiagnosticsReports(config);
|
|
1644
|
+
if (cleaned.crashReports > 0) {
|
|
1645
|
+
log('info', `cleanup: pruned ${cleaned.crashReports} old crash-diagnostics report(s)`);
|
|
1646
|
+
}
|
|
1647
|
+
} catch (e) { log('warn', `pruneCrashDiagnosticsReports: ${e.message}`); }
|
|
1648
|
+
|
|
1636
1649
|
return cleaned;
|
|
1637
1650
|
}
|
|
1638
1651
|
|
package/engine/shared.js
CHANGED
|
@@ -58,6 +58,13 @@ const MINIONS_DIR = process.env.MINIONS_TEST_DIR || resolveMinionsHome(false, {
|
|
|
58
58
|
preferSourceCheckout: true,
|
|
59
59
|
});
|
|
60
60
|
const ENGINE_DIR = path.join(MINIONS_DIR, 'engine');
|
|
61
|
+
// W-mr2c4i8m0004da94: Node's built-in `--diagnostic-dir` target for the
|
|
62
|
+
// engine.js process's crash-diagnostics report (see
|
|
63
|
+
// getEngineCrashDiagnosticsEnv / pruneCrashDiagnosticsReports below and
|
|
64
|
+
// docs/diagnostics-crash-reports.md). Sibling of the existing
|
|
65
|
+
// engine/diagnostics/ heap-snapshot dir, own subfolder so retention sweeps
|
|
66
|
+
// don't collide with heap-snapshot pruning.
|
|
67
|
+
const CRASH_REPORTS_DIR = path.join(ENGINE_DIR, 'diagnostics', 'crash-reports');
|
|
61
68
|
const CONTROL_PATH = path.join(ENGINE_DIR, 'control.json');
|
|
62
69
|
const COOLDOWNS_PATH = path.join(ENGINE_DIR, 'cooldowns.json');
|
|
63
70
|
// W-mp60tw0u000j3931: Persistent cross-restart engine state (migration markers,
|
|
@@ -1633,6 +1640,69 @@ function openAppendLogFd(name, dir, opts) {
|
|
|
1633
1640
|
}
|
|
1634
1641
|
}
|
|
1635
1642
|
|
|
1643
|
+
// W-mr2c4i8m0004da94: proactive crash diagnostics for the engine.js node
|
|
1644
|
+
// process on Windows. Follow-up from W-mr2azk6i (2026-07-01 crash-loop
|
|
1645
|
+
// investigation): 4 silent engine.js deaths with no exception in
|
|
1646
|
+
// engine-stdio.log, no Windows Event Viewer entries, and no crash dumps —
|
|
1647
|
+
// this box had no crash-event logging wired up for a genuine native/AV
|
|
1648
|
+
// crash. Node's own diagnostic-report feature is a zero-dependency fix:
|
|
1649
|
+
// `--report-on-fatalerror` writes a JSON report on unrecoverable V8 errors
|
|
1650
|
+
// (OOM, stack overflow, native-addon segfault) and `--report-on-signal`
|
|
1651
|
+
// lets an operator request one on demand (SIGUSR2 on POSIX, SIGBREAK on
|
|
1652
|
+
// Windows) without installing anything (e.g. Sysinternals ProcDump).
|
|
1653
|
+
//
|
|
1654
|
+
// Callers: bin/minions.js (CLI `start`/`restart` spawn) and
|
|
1655
|
+
// engine/supervisor.js (respawn-on-death). Both spawn engine.js as a fresh
|
|
1656
|
+
// process before any config is loaded into memory, so this reads config.json
|
|
1657
|
+
// directly rather than expecting an already-parsed config object — pass the
|
|
1658
|
+
// parsed object in if the caller already has it (e.g. from its own
|
|
1659
|
+
// best-effort config.json read), or `null`/`undefined` to use the defaults.
|
|
1660
|
+
//
|
|
1661
|
+
// Idempotent: if the caller's NODE_OPTIONS already carries
|
|
1662
|
+
// `--diagnostic-dir=`, returns `{}` unchanged so the CLI's spawn -> a later
|
|
1663
|
+
// supervisor respawn -> another respawn chain never piles up duplicate
|
|
1664
|
+
// flags (child processes inherit NODE_OPTIONS from their parent env).
|
|
1665
|
+
function getEngineCrashDiagnosticsEnv(config, baseEnv) {
|
|
1666
|
+
const env = baseEnv || process.env;
|
|
1667
|
+
const cfg = (config && config.engine && config.engine.crashDiagnostics) || {};
|
|
1668
|
+
const defaults = ENGINE_DEFAULTS.crashDiagnostics || {};
|
|
1669
|
+
const enabled = cfg.enabled !== undefined ? cfg.enabled !== false : defaults.enabled !== false;
|
|
1670
|
+
if (!enabled) return {};
|
|
1671
|
+
const existing = String(env.NODE_OPTIONS || '');
|
|
1672
|
+
if (existing.includes('--diagnostic-dir=')) return {};
|
|
1673
|
+
try { fs.mkdirSync(CRASH_REPORTS_DIR, { recursive: true }); } catch { /* best effort — Node still tries to write on crash */ }
|
|
1674
|
+
const flags = `--report-on-fatalerror --report-on-signal --diagnostic-dir=${CRASH_REPORTS_DIR}`;
|
|
1675
|
+
return { NODE_OPTIONS: existing ? `${existing} ${flags}` : flags };
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
// Prune `report.*.json` files in CRASH_REPORTS_DIR down to the most-recent
|
|
1679
|
+
// `retainCount` (config.engine.crashDiagnostics.retainCount, default 20) by
|
|
1680
|
+
// mtime, mirroring dashboard.js's `_heapSnapshotPrune` retention pattern so
|
|
1681
|
+
// a leak-hunt session (or a genuine crash-loop) doesn't fill the disk with
|
|
1682
|
+
// reports unbounded. Best-effort: missing dir / unreadable stat / failed
|
|
1683
|
+
// unlink are swallowed. Called from engine/cleanup.js#runCleanup (fires
|
|
1684
|
+
// every ENGINE_DEFAULTS.cleanupEvery ticks, ~10 min at default tick
|
|
1685
|
+
// interval) so retention happens automatically without a dedicated sweep.
|
|
1686
|
+
function pruneCrashDiagnosticsReports(config) {
|
|
1687
|
+
const cfg = (config && config.engine && config.engine.crashDiagnostics) || {};
|
|
1688
|
+
const defaults = ENGINE_DEFAULTS.crashDiagnostics || {};
|
|
1689
|
+
const retain = Number.isFinite(cfg.retainCount) ? cfg.retainCount : (defaults.retainCount || 20);
|
|
1690
|
+
let names;
|
|
1691
|
+
try { names = fs.readdirSync(CRASH_REPORTS_DIR); } catch { return 0; }
|
|
1692
|
+
const candidates = names.filter(n => n.startsWith('report.') && n.endsWith('.json'));
|
|
1693
|
+
if (candidates.length <= retain) return 0;
|
|
1694
|
+
const stamped = candidates.map(n => {
|
|
1695
|
+
try { return { n, mtime: fs.statSync(path.join(CRASH_REPORTS_DIR, n)).mtimeMs }; }
|
|
1696
|
+
catch { return { n, mtime: 0 }; }
|
|
1697
|
+
});
|
|
1698
|
+
stamped.sort((a, b) => b.mtime - a.mtime); // newest first
|
|
1699
|
+
let removed = 0;
|
|
1700
|
+
for (const s of stamped.slice(retain)) {
|
|
1701
|
+
try { fs.unlinkSync(path.join(CRASH_REPORTS_DIR, s.n)); removed++; } catch { /* best effort */ }
|
|
1702
|
+
}
|
|
1703
|
+
return removed;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1636
1706
|
function withFileLock(lockPath, fn, {
|
|
1637
1707
|
timeoutMs = 5000,
|
|
1638
1708
|
retryDelayMs = 25,
|
|
@@ -3156,6 +3226,19 @@ const ENGINE_DEFAULTS = {
|
|
|
3156
3226
|
// at the default 10s tickInterval. Set to 0 (or any non-positive integer)
|
|
3157
3227
|
// to disable both the log emission and sidecar write cleanly (operator opt-out).
|
|
3158
3228
|
memoryBaselineEveryTicks: 6,
|
|
3229
|
+
// W-mr2c4i8m0004da94: proactive crash diagnostics for the engine.js
|
|
3230
|
+
// node.exe process. When enabled (default), every spawn of engine.js
|
|
3231
|
+
// (bin/minions.js `start`/`restart` AND engine/supervisor.js respawn-on-
|
|
3232
|
+
// death) gets NODE_OPTIONS += `--report-on-fatalerror --report-on-signal
|
|
3233
|
+
// --diagnostic-dir=<CRASH_REPORTS_DIR>` so a fatal V8 error (OOM, stack
|
|
3234
|
+
// overflow, native-addon crash) or an operator-sent signal
|
|
3235
|
+
// (SIGUSR2 POSIX / SIGBREAK Windows) leaves a JSON diagnostic report
|
|
3236
|
+
// instead of a silent death. See getEngineCrashDiagnosticsEnv,
|
|
3237
|
+
// pruneCrashDiagnosticsReports, and docs/diagnostics-crash-reports.md.
|
|
3238
|
+
crashDiagnostics: {
|
|
3239
|
+
enabled: true,
|
|
3240
|
+
retainCount: 20, // report.*.json files kept in CRASH_REPORTS_DIR; oldest pruned during runCleanup (cleanupEvery ticks)
|
|
3241
|
+
},
|
|
3159
3242
|
stalledDispatchSweepEvery: 120, // stalled-dispatch retry sweep (~20 min at default 10s tick) — only fires when all agents idle
|
|
3160
3243
|
// W-mp5trwh60008386d: per-PR 404 must repeat across N consecutive successful base-repo probes
|
|
3161
3244
|
// before flipping a PR to `abandoned`. A single 404 on `repos/{slug}/pulls/{n}` can be a transient
|
|
@@ -9091,6 +9174,9 @@ function createBackoffTracker({ baseMs = 30000, maxMs = 10 * 60 * 1000 } = {}) {
|
|
|
9091
9174
|
module.exports = {
|
|
9092
9175
|
MINIONS_DIR,
|
|
9093
9176
|
ENGINE_DIR,
|
|
9177
|
+
CRASH_REPORTS_DIR,
|
|
9178
|
+
getEngineCrashDiagnosticsEnv,
|
|
9179
|
+
pruneCrashDiagnosticsReports,
|
|
9094
9180
|
resolveEngineCacheDir,
|
|
9095
9181
|
openUrlInBrowser,
|
|
9096
9182
|
CONTROL_PATH,
|
package/engine/supervisor.js
CHANGED
|
@@ -347,6 +347,18 @@ function _sqliteSpawnFlags() {
|
|
|
347
347
|
return ['--experimental-sqlite'];
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
+
// W-mr2c4i8m0004da94: crash-diagnostics env additions for a supervisor-
|
|
351
|
+
// initiated respawn. Best-effort config.json read — the supervisor spawns
|
|
352
|
+
// engine.js before any config is loaded into memory, mirroring the same
|
|
353
|
+
// read bin/minions.js does for its own engine spawns.
|
|
354
|
+
function _engineCrashDiagnosticsEnv() {
|
|
355
|
+
const shared = _sharedOrNull();
|
|
356
|
+
if (!shared || typeof shared.getEngineCrashDiagnosticsEnv !== 'function') return {};
|
|
357
|
+
let cfg = null;
|
|
358
|
+
try { cfg = JSON.parse(fs.readFileSync(path.join(MINIONS_DIR, 'config.json'), 'utf8')); } catch { /* config may not exist */ }
|
|
359
|
+
try { return shared.getEngineCrashDiagnosticsEnv(cfg); } catch { return {}; }
|
|
360
|
+
}
|
|
361
|
+
|
|
350
362
|
function spawnEngine() {
|
|
351
363
|
const out = openAppendFd('engine-stdio.log');
|
|
352
364
|
const err = openAppendFd('engine-stdio.log');
|
|
@@ -355,6 +367,7 @@ function spawnEngine() {
|
|
|
355
367
|
stdio: ['ignore', out, err],
|
|
356
368
|
detached: true,
|
|
357
369
|
windowsHide: true,
|
|
370
|
+
env: { ...process.env, ..._engineCrashDiagnosticsEnv() },
|
|
358
371
|
});
|
|
359
372
|
proc.unref();
|
|
360
373
|
return proc.pid;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2309",
|
|
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"
|