@yemi33/minions 0.1.2179 → 0.1.2180
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/README.md +7 -5
- package/bin/minions.js +15 -6
- package/dashboard/js/memory-panel.js +62 -0
- package/dashboard/js/refresh.js +18 -0
- package/dashboard/js/render-other.js +142 -1
- package/dashboard/js/render-work-items.js +18 -1
- package/dashboard/js/settings.js +23 -0
- package/dashboard/pages/engine-memory-panel.html +7 -0
- package/dashboard/pages/tools.html +8 -0
- package/dashboard.js +466 -3
- package/docs/diagnostics-memory.md +446 -0
- package/docs/harness-propagation.md +273 -0
- package/docs/human-vs-automated.md +1 -1
- package/docs/runtime-adapters.md +5 -0
- package/engine/cli.js +24 -5
- package/engine/preflight.js +265 -0
- package/engine/queries.js +192 -15
- package/engine/runtimes/claude.js +36 -0
- package/engine/runtimes/codex.js +19 -0
- package/engine/runtimes/copilot.js +27 -36
- package/engine/shared.js +277 -13
- package/engine/spawn-agent.js +178 -12
- package/engine.js +232 -3
- package/package.json +1 -1
package/engine.js
CHANGED
|
@@ -175,6 +175,13 @@ const { runPostCompletionHooks, updateWorkItemStatus, syncPrdItemStatus, reconci
|
|
|
175
175
|
const diagnosticsMemory = require('./engine/diagnostics-memory');
|
|
176
176
|
const DIAGNOSTICS_MEMORY_PATH = path.join(ENGINE_DIR, 'diagnostics-memory.json');
|
|
177
177
|
|
|
178
|
+
// P-e5f6a7b8 — sentinel consumed each tick; dashboard.js writes it after
|
|
179
|
+
// validating the operator confirm token. We pick up the iso the dashboard
|
|
180
|
+
// chose so the engine snapshot file lands at a predictable path the
|
|
181
|
+
// dashboard can directly poll for.
|
|
182
|
+
const DIAGNOSTICS_DIR = path.join(ENGINE_DIR, 'diagnostics');
|
|
183
|
+
const HEAP_SNAPSHOT_REQUEST_PATH = path.join(DIAGNOSTICS_DIR, 'heap-snapshot-request.json');
|
|
184
|
+
|
|
178
185
|
// ─── Agent Spawner ──────────────────────────────────────────────────────────
|
|
179
186
|
|
|
180
187
|
const activeProcesses = new Map(); // dispatchId → { proc, agentId, startedAt }
|
|
@@ -1701,9 +1708,54 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1701
1708
|
// stages now short-circuit alongside any other read-only WI (see the gate at
|
|
1702
1709
|
// `if (branchName && READ_ONLY_ROOT_TASK_TYPES.has(type))` below).
|
|
1703
1710
|
const _preBranchName = meta?.branch ? sanitizeBranch(meta.branch) : null;
|
|
1711
|
+
|
|
1712
|
+
// ── Per-WI meta.workdir override (P-714ef144) ─────────────────────────
|
|
1713
|
+
// Optional relative POSIX subpath on the work item that lands the agent
|
|
1714
|
+
// at <base>/<workdir> instead of <base>. Used for monorepo subpackage
|
|
1715
|
+
// dispatches so the runtime CLI's cwd-rooted skill discovery surfaces
|
|
1716
|
+
// only the target package's `.claude/skills/<name>/SKILL.md` files.
|
|
1717
|
+
// Validation runs at dispatch time (the dashboard also validates on
|
|
1718
|
+
// create/update — this is defense-in-depth for ad-hoc dispatches and
|
|
1719
|
+
// upgrades that re-dispatch pre-validator WIs). Containment escapes are
|
|
1720
|
+
// non-retryable: the operator must fix the WI's meta.workdir or remove
|
|
1721
|
+
// it before a retry would succeed.
|
|
1722
|
+
const _rawWorkdir = meta?.item?.meta?.workdir;
|
|
1723
|
+
const _wdValidation = shared.validateWorkItemWorkdir(_rawWorkdir);
|
|
1724
|
+
if (!_wdValidation.valid) {
|
|
1725
|
+
const _wiId = meta?.item?.id || meta?.workItemId || id;
|
|
1726
|
+
log('warn', `spawnAgent: meta.workdir validation rejected dispatch ${id} (WI ${_wiId}): ${_wdValidation.error}`);
|
|
1727
|
+
try {
|
|
1728
|
+
writeInboxAlert(`invalid-workdir-${_wiId}`, [
|
|
1729
|
+
`# Invalid meta.workdir on ${_wiId}`,
|
|
1730
|
+
``,
|
|
1731
|
+
`Dispatch \`${id}\` for agent \`${agentId}\` was rejected before spawn.`,
|
|
1732
|
+
``,
|
|
1733
|
+
`**Reason:** ${_wdValidation.error}`,
|
|
1734
|
+
``,
|
|
1735
|
+
`**Submitted value:** \`${typeof _rawWorkdir === 'string' ? _rawWorkdir : JSON.stringify(_rawWorkdir)}\``,
|
|
1736
|
+
``,
|
|
1737
|
+
`**Project:** \`${project?.name || '(unknown)'}\` (localPath: \`${project?.localPath || '(none)'}\`)`,
|
|
1738
|
+
``,
|
|
1739
|
+
`meta.workdir must be a relative POSIX subpath under the project root (or worktree, for code-mutating types). Examples: \`packages/foo\`, \`apps/dashboard\`. Absolute paths, drive-letter prefixes, \`..\` segments, and null bytes are rejected.`,
|
|
1740
|
+
``,
|
|
1741
|
+
`Fix the WI's \`meta.workdir\` field (or remove it to dispatch at the project root) and re-dispatch.`,
|
|
1742
|
+
].join('\n'));
|
|
1743
|
+
} catch (e) { log('warn', `invalid-workdir inbox alert write failed: ${e.message}`); }
|
|
1744
|
+
completeDispatch(
|
|
1745
|
+
id,
|
|
1746
|
+
DISPATCH_RESULT.ERROR,
|
|
1747
|
+
_wdValidation.error.slice(0, 800),
|
|
1748
|
+
'meta.workdir validation rejected this dispatch — fix the WI subpath or remove it before re-dispatch.',
|
|
1749
|
+
{ failureClass: FAILURE_CLASS.INVALID_WORKDIR, agentRetryable: false },
|
|
1750
|
+
);
|
|
1751
|
+
cleanupTempAgent(agentId);
|
|
1752
|
+
return null;
|
|
1753
|
+
}
|
|
1754
|
+
const validatedWorkdir = _wdValidation.value; // null when unset / empty
|
|
1755
|
+
|
|
1704
1756
|
let cwd, worktreeRootDir, liveMode = false;
|
|
1705
1757
|
try {
|
|
1706
|
-
({ cwd, worktreeRootDir, liveMode = false } = shared.resolveSpawnPaths(project, type, MINIONS_DIR));
|
|
1758
|
+
({ cwd, worktreeRootDir, liveMode = false } = shared.resolveSpawnPaths(project, type, MINIONS_DIR, { workdir: validatedWorkdir }));
|
|
1707
1759
|
} catch (rootErr) {
|
|
1708
1760
|
if (rootErr?.code === 'WORKTREE_ROOTDIR_COLLAPSED_TO_DRIVE_ROOT' || rootErr?.code === 'WORKTREE_ROOTDIR_MISSING_BASE') {
|
|
1709
1761
|
log('error', `spawnAgent: project rootDir resolution failed for ${id}: ${rootErr.message}`);
|
|
@@ -1718,6 +1770,38 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1718
1770
|
cleanupTempAgent(agentId);
|
|
1719
1771
|
return null;
|
|
1720
1772
|
}
|
|
1773
|
+
if (rootErr?.code === 'INVALID_WORKDIR') {
|
|
1774
|
+
// Post-resolve containment escape from live or read-only branch (the
|
|
1775
|
+
// pre-resolve validator above already caught the common cases; this
|
|
1776
|
+
// catches symlink-style attacks where the literal subpath looks fine
|
|
1777
|
+
// but path.resolve lands outside the base).
|
|
1778
|
+
const _wiId = meta?.item?.id || meta?.workItemId || id;
|
|
1779
|
+
log('warn', `spawnAgent: workdir post-resolve escape for ${id} (WI ${_wiId}): ${rootErr.message}`);
|
|
1780
|
+
try {
|
|
1781
|
+
writeInboxAlert(`invalid-workdir-${_wiId}`, [
|
|
1782
|
+
`# Invalid meta.workdir on ${_wiId} (post-resolve escape)`,
|
|
1783
|
+
``,
|
|
1784
|
+
`Dispatch \`${id}\` for agent \`${agentId}\` was rejected by the resolver containment guard.`,
|
|
1785
|
+
``,
|
|
1786
|
+
`**Reason:** ${rootErr.message}`,
|
|
1787
|
+
``,
|
|
1788
|
+
`**Submitted value:** \`${validatedWorkdir || ''}\``,
|
|
1789
|
+
``,
|
|
1790
|
+
`**Project:** \`${project?.name || '(unknown)'}\` (localPath: \`${project?.localPath || '(none)'}\`)`,
|
|
1791
|
+
``,
|
|
1792
|
+
`The literal subpath passed shape validation but path.resolve landed outside the base directory — usually a symlink in the project root pointing elsewhere. Fix the WI's meta.workdir or remove the offending symlink before re-dispatch.`,
|
|
1793
|
+
].join('\n'));
|
|
1794
|
+
} catch (e) { log('warn', `invalid-workdir inbox alert write failed: ${e.message}`); }
|
|
1795
|
+
completeDispatch(
|
|
1796
|
+
id,
|
|
1797
|
+
DISPATCH_RESULT.ERROR,
|
|
1798
|
+
rootErr.message.slice(0, 800),
|
|
1799
|
+
'meta.workdir resolved outside the project/worktree base — fix the subpath or remove it.',
|
|
1800
|
+
{ failureClass: FAILURE_CLASS.INVALID_WORKDIR, agentRetryable: false },
|
|
1801
|
+
);
|
|
1802
|
+
cleanupTempAgent(agentId);
|
|
1803
|
+
return null;
|
|
1804
|
+
}
|
|
1721
1805
|
throw rootErr;
|
|
1722
1806
|
}
|
|
1723
1807
|
// Legacy local alias: downstream git ops (worktree add, prune, fetch) and
|
|
@@ -3086,6 +3170,15 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
3086
3170
|
// Other runtimes ignore the opt (their buildSpawnFlags don't read it), so no
|
|
3087
3171
|
// runtime.name branch here.
|
|
3088
3172
|
const resolvedDisabledMcpServers = shared.resolveCopilotAgentDisabledMcpServers(agentConfig, engineConfig);
|
|
3173
|
+
// P-49e1c8b7 — hermetic harness opt-out (per-agent override allowed).
|
|
3174
|
+
// When true, this dispatch:
|
|
3175
|
+
// - skips Claude workspace .mcp.json pre-approval (preApproveWorkspaceMcps),
|
|
3176
|
+
// - skips project-local-on-main `--project-harness-dir` propagation,
|
|
3177
|
+
// - forwards `--hermetic-harness` to spawn-agent.js so computeAddDirs
|
|
3178
|
+
// returns [minionsDir] only (user-asset dirs stripped from --add-dir).
|
|
3179
|
+
// Independent of copilotDisableBuiltinMcps and copilotSuppressAgentsMd,
|
|
3180
|
+
// which retain their existing semantics.
|
|
3181
|
+
const resolvedHermetic = shared.resolveAgentHermeticHarness(agentConfig, engineConfig);
|
|
3089
3182
|
|
|
3090
3183
|
// W-mpg6isvy000xca4d — On retry after FAILURE_CLASS.MODEL_UNAVAILABLE, swap
|
|
3091
3184
|
// to the runtime-appropriate fallback model. Two paths gated on
|
|
@@ -3145,6 +3238,39 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
3145
3238
|
|
|
3146
3239
|
// MCP servers: agents inherit from ~/.claude.json directly as Claude Code processes.
|
|
3147
3240
|
// No --mcp-config needed — avoids redundant config and ensures agents always have latest servers.
|
|
3241
|
+
//
|
|
3242
|
+
// P-7d31a06b — When the runtime is Claude AND the worktree has a `.mcp.json`,
|
|
3243
|
+
// pre-warm `~/.claude.json` projects.<worktreePath>.enabledMcpjsonServers so
|
|
3244
|
+
// Claude's first call doesn't show the project-MCP trust prompt (which
|
|
3245
|
+
// --dangerously-skip-permissions silently suppresses → agent runs without the
|
|
3246
|
+
// workspace MCPs connected). Helper internally no-ops for non-Claude runtimes
|
|
3247
|
+
// and when engine.claudePreApproveWorkspaceMcps is false (no runtime.name
|
|
3248
|
+
// check at this call site, per CLAUDE.md rule). Best-effort: NEVER block dispatch.
|
|
3249
|
+
//
|
|
3250
|
+
// P-49e1c8b7 — Skipped entirely when resolvedHermetic is true: a hermetic
|
|
3251
|
+
// dispatch wants a known-empty harness surface, so the workspace .mcp.json
|
|
3252
|
+
// approval must not be written.
|
|
3253
|
+
if (resolvedHermetic) {
|
|
3254
|
+
log('debug', `Hermetic harness: skipping workspace MCP pre-approval for ${id}`);
|
|
3255
|
+
} else {
|
|
3256
|
+
try {
|
|
3257
|
+
const result = preApproveWorkspaceMcps({
|
|
3258
|
+
runtimeName,
|
|
3259
|
+
worktreePath,
|
|
3260
|
+
homeDir: os.homedir(),
|
|
3261
|
+
engineConfig,
|
|
3262
|
+
mutateJsonFileLocked: shared.mutateJsonFileLocked,
|
|
3263
|
+
});
|
|
3264
|
+
if (result.wrote) {
|
|
3265
|
+
log('info', `Pre-approved ${result.servers.length} workspace MCP server(s) in ~/.claude.json for ${worktreePath}: ${result.servers.join(', ')}`);
|
|
3266
|
+
} else if (result.reason !== 'no-workspace-mcp' && result.reason !== 'not-claude' && result.reason !== 'no-worktree') {
|
|
3267
|
+
log('debug', `Skipped workspace MCP pre-approval for ${id} (reason=${result.reason})`);
|
|
3268
|
+
}
|
|
3269
|
+
} catch (err) {
|
|
3270
|
+
log('warn', `Workspace MCP pre-approval failed for ${id} (non-fatal): ${err.message}`);
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3148
3274
|
_phaseT.afterRuntime = Date.now();
|
|
3149
3275
|
|
|
3150
3276
|
log('info', `Spawning agent: ${agentId} (${id}) in ${cwd}`);
|
|
@@ -3202,7 +3328,70 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
3202
3328
|
// Spawn via wrapper script — node directly (no bash intermediary)
|
|
3203
3329
|
// spawn-agent.js handles CLAUDECODE env cleanup and claude binary resolution
|
|
3204
3330
|
const spawnScript = path.join(ENGINE_DIR, 'spawn-agent.js');
|
|
3205
|
-
|
|
3331
|
+
|
|
3332
|
+
// ── P-08b62d49 — project-local-on-main harness propagation ──────────────
|
|
3333
|
+
// The worktree-uncommitted footgun (docs/harness-propagation.md): when a
|
|
3334
|
+
// mutating dispatch runs in a fresh `git worktree add` (worktreePath !==
|
|
3335
|
+
// project.localPath), uncommitted `<repo>/.claude/skills/foo/SKILL.md` etc.
|
|
3336
|
+
// on the operator's main checkout are invisible to the runtime CLI because
|
|
3337
|
+
// its native discovery is rooted at cwd. Union the project-scope harness
|
|
3338
|
+
// dirs (skills + commands) that exist under `project.localPath` but NOT
|
|
3339
|
+
// under `worktreePath`, and forward them to spawn-agent as
|
|
3340
|
+
// `--project-harness-dir <dir>` so `computeAddDirs` surfaces them via
|
|
3341
|
+
// `--add-dir`. Gated by engine.harnessPropagateProjectLocal (default true).
|
|
3342
|
+
// No-op for live-checkout mode (worktreePath === null) and read-only types
|
|
3343
|
+
// (also worktreePath === null per resolveSpawnPaths).
|
|
3344
|
+
//
|
|
3345
|
+
// P-49e1c8b7 — Also skipped entirely when resolvedHermetic is true: hermetic
|
|
3346
|
+
// dispatches want a known-empty harness surface so neither user-asset dirs
|
|
3347
|
+
// (filtered inside computeAddDirs) nor project-local-on-main dirs (filtered
|
|
3348
|
+
// here) reach the agent. We skip the queries.getProjectHarnesses(...) call
|
|
3349
|
+
// too — saves the fs scan when the result will be discarded.
|
|
3350
|
+
//
|
|
3351
|
+
// P-714ef144 — when meta.workdir is set, the filter helper clips both the
|
|
3352
|
+
// project-side and worktree-side anchors to <base>/<workdir>, so a
|
|
3353
|
+
// `packages/foo` dispatch never sees harness dirs from sibling packages.
|
|
3354
|
+
// The clipping is purely additive on top of the existing project-vs-worktree
|
|
3355
|
+
// disjoint filter; when workdir is null the helper produces the same result
|
|
3356
|
+
// as the legacy inline loop.
|
|
3357
|
+
const projectHarnessArgs = [];
|
|
3358
|
+
if (!resolvedHermetic
|
|
3359
|
+
&& engineConfig.harnessPropagateProjectLocal !== false
|
|
3360
|
+
&& worktreePath
|
|
3361
|
+
&& project?.localPath
|
|
3362
|
+
&& path.resolve(worktreePath) !== path.resolve(project.localPath)) {
|
|
3363
|
+
try {
|
|
3364
|
+
const harnesses = queries.getProjectHarnesses(project);
|
|
3365
|
+
const candidateDirs = [
|
|
3366
|
+
...(harnesses.skills || []),
|
|
3367
|
+
...(harnesses.commands || []),
|
|
3368
|
+
]
|
|
3369
|
+
.map((entry) => entry?.dir)
|
|
3370
|
+
.filter((d) => typeof d === 'string' && d);
|
|
3371
|
+
const filteredDirs = shared.filterProjectHarnessDirsForWorkdir(candidateDirs, {
|
|
3372
|
+
projectLocalPath: project.localPath,
|
|
3373
|
+
worktreePath,
|
|
3374
|
+
workdir: validatedWorkdir,
|
|
3375
|
+
});
|
|
3376
|
+
for (const abs of filteredDirs) {
|
|
3377
|
+
if (!fs.existsSync(abs)) continue;
|
|
3378
|
+
projectHarnessArgs.push('--project-harness-dir', abs);
|
|
3379
|
+
}
|
|
3380
|
+
if (projectHarnessArgs.length) {
|
|
3381
|
+
log('debug', `Project-local harness propagation: ${projectHarnessArgs.length / 2} dir(s) for ${id} (${worktreePath}${validatedWorkdir ? ', workdir=' + validatedWorkdir : ''})`);
|
|
3382
|
+
}
|
|
3383
|
+
} catch (err) {
|
|
3384
|
+
log('warn', `Project-local harness propagation failed for ${id} (non-fatal): ${err.message}`);
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3388
|
+
// P-49e1c8b7 — hermetic harness opt-out forwarded to spawn-agent.js so
|
|
3389
|
+
// computeAddDirs returns [minionsDir] only (user-asset dirs stripped).
|
|
3390
|
+
// Append BEFORE projectHarnessArgs (which will be empty when hermetic, but
|
|
3391
|
+
// ordering keeps the intent explicit in process listings).
|
|
3392
|
+
const hermeticArgs = resolvedHermetic ? ['--hermetic-harness'] : [];
|
|
3393
|
+
|
|
3394
|
+
const spawnArgs = [spawnScript, promptPath, sysPromptPath, ...args, ...hermeticArgs, ...projectHarnessArgs];
|
|
3206
3395
|
|
|
3207
3396
|
// Live output file — stamped BEFORE child process is spawned (#W-mo248lkjwgsu).
|
|
3208
3397
|
// Writing the stub pre-spawn lets the orphan detector distinguish three failure modes
|
|
@@ -3589,7 +3778,12 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
3589
3778
|
let resumeProc;
|
|
3590
3779
|
try {
|
|
3591
3780
|
// detached so the resumed steering session also survives engine death (matches initial spawn)
|
|
3592
|
-
|
|
3781
|
+
// P-08b62d49 — also include projectHarnessArgs so the resumed runtime
|
|
3782
|
+
// can still reach uncommitted project-local harness dirs (the runtime
|
|
3783
|
+
// CLI re-indexes asset dirs on every spawn, including this resume).
|
|
3784
|
+
// P-49e1c8b7 — same for --hermetic-harness so the resumed dispatch
|
|
3785
|
+
// keeps the known-empty harness contract.
|
|
3786
|
+
resumeProc = runFile(process.execPath, [spawnScript, steerPromptPath, sysPromptPath, ...resumeArgs, ...hermeticArgs, ...projectHarnessArgs], {
|
|
3593
3787
|
cwd,
|
|
3594
3788
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
3595
3789
|
env: childEnv,
|
|
@@ -9237,6 +9431,41 @@ async function tickInner() {
|
|
|
9237
9431
|
// cache (single-object latest sample) — gitignored and exempt from the
|
|
9238
9432
|
// SQL-first state rule, same as engine/dashboard-port.json.
|
|
9239
9433
|
safe('memoryBaseline', () => emitMemoryBaseline(tickCount));
|
|
9434
|
+
|
|
9435
|
+
// 7. Operator-driven heap snapshot capture (P-e5f6a7b8).
|
|
9436
|
+
// Dashboard's POST /api/diagnostics/heap-snapshot drops a sentinel in
|
|
9437
|
+
// engine/diagnostics/heap-snapshot-request.json after capturing its own
|
|
9438
|
+
// heap. We pick it up on the next tick, write the engine-side
|
|
9439
|
+
// .heapsnapshot, then remove the sentinel so the dashboard handler can
|
|
9440
|
+
// detect completion. The v8.writeHeapSnapshot call stalls THIS process
|
|
9441
|
+
// for several seconds — the dashboard polls with a 30s timeout.
|
|
9442
|
+
safe('heapSnapshotRequest', () => processHeapSnapshotRequest());
|
|
9443
|
+
}
|
|
9444
|
+
|
|
9445
|
+
function processHeapSnapshotRequest() {
|
|
9446
|
+
if (!fs.existsSync(HEAP_SNAPSHOT_REQUEST_PATH)) return;
|
|
9447
|
+
let req = null;
|
|
9448
|
+
try {
|
|
9449
|
+
req = JSON.parse(fs.readFileSync(HEAP_SNAPSHOT_REQUEST_PATH, 'utf8'));
|
|
9450
|
+
} catch (e) {
|
|
9451
|
+
log('warn', `heap-snapshot-request parse failed: ${e.message}`);
|
|
9452
|
+
try { safeUnlink(HEAP_SNAPSHOT_REQUEST_PATH); } catch { /* best effort */ }
|
|
9453
|
+
return;
|
|
9454
|
+
}
|
|
9455
|
+
const iso = (req && typeof req.requestedAt === 'string') ? req.requestedAt : new Date().toISOString();
|
|
9456
|
+
const safeIso = String(iso).replace(/[:.]/g, '-');
|
|
9457
|
+
const outPath = path.join(DIAGNOSTICS_DIR, `heap-engine-${safeIso}.heapsnapshot`);
|
|
9458
|
+
const v8 = require('v8');
|
|
9459
|
+
try {
|
|
9460
|
+
fs.mkdirSync(DIAGNOSTICS_DIR, { recursive: true });
|
|
9461
|
+
log('info', `HEAP_SNAPSHOT engine capturing -> ${outPath} (this stalls the engine for several seconds)`);
|
|
9462
|
+
v8.writeHeapSnapshot(outPath);
|
|
9463
|
+
log('info', `HEAP_SNAPSHOT engine wrote ${outPath}`);
|
|
9464
|
+
} catch (e) {
|
|
9465
|
+
log('warn', `heap-snapshot writeHeapSnapshot failed: ${e.message}`);
|
|
9466
|
+
} finally {
|
|
9467
|
+
try { safeUnlink(HEAP_SNAPSHOT_REQUEST_PATH); } catch { /* best effort */ }
|
|
9468
|
+
}
|
|
9240
9469
|
}
|
|
9241
9470
|
|
|
9242
9471
|
function emitMemoryBaseline(tickN) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2180",
|
|
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"
|