@yemi33/minions 0.1.1736 → 0.1.1737
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 -0
- package/engine/cleanup.js +27 -3
- package/engine/cli.js +17 -7
- package/engine/copilot-models.json +1 -1
- package/engine/dispatch.js +29 -11
- package/engine/llm.js +107 -24
- package/engine/shared.js +1 -0
- package/engine.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/engine/cleanup.js
CHANGED
|
@@ -8,7 +8,7 @@ const path = require('path');
|
|
|
8
8
|
const shared = require('./shared');
|
|
9
9
|
const queries = require('./queries');
|
|
10
10
|
|
|
11
|
-
const { exec, execSilent, log, ts, ENGINE_DEFAULTS } = shared;
|
|
11
|
+
const { exec, execAsync, execSilent, log, ts, ENGINE_DEFAULTS } = shared;
|
|
12
12
|
const { safeJson, safeWrite, safeReadDir, mutateCooldowns, mutateWorkItems, mutateJsonFileLocked, getProjects, projectWorkItemsPath, projectPrPath,
|
|
13
13
|
sanitizeBranch, KB_CATEGORIES } = shared;
|
|
14
14
|
const { getDispatch, getAgentStatus } = queries;
|
|
@@ -56,6 +56,15 @@ function getWorktreeBranch(wtPath) {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
async function getWorktreeBranchAsync(wtPath) {
|
|
60
|
+
try {
|
|
61
|
+
const out = await execAsync(`git -C "${wtPath}" branch --show-current`, { encoding: 'utf8', timeout: 5000 });
|
|
62
|
+
return (out || '').toString().trim();
|
|
63
|
+
} catch {
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
59
68
|
let _orphanPidProcessNamesCache = null;
|
|
60
69
|
function _orphanPidProcessNames() {
|
|
61
70
|
if (_orphanPidProcessNamesCache) return _orphanPidProcessNamesCache;
|
|
@@ -134,7 +143,7 @@ function _killProcessInWorktree(dir, activeProcesses, activeIds) {
|
|
|
134
143
|
|
|
135
144
|
// ─── Cleanup Orchestrator ────────────────────────────────────────────────────
|
|
136
145
|
|
|
137
|
-
function runCleanup(config, verbose = false) {
|
|
146
|
+
async function runCleanup(config, verbose = false) {
|
|
138
147
|
const activeProcesses = engine().activeProcesses;
|
|
139
148
|
const projects = getProjects(config);
|
|
140
149
|
let cleaned = { tempFiles: 0, liveOutputs: 0, worktrees: 0, zombies: 0 };
|
|
@@ -248,11 +257,26 @@ function runCleanup(config, verbose = false) {
|
|
|
248
257
|
const dispatch = getDispatch();
|
|
249
258
|
const activeDispatchIds = new Set((dispatch.active || []).map(d => d.id));
|
|
250
259
|
|
|
260
|
+
// Probe `git branch --show-current` for every worktree in chunks of 5.
|
|
261
|
+
// Sequential probing was the dominant cost in the cleanup phase
|
|
262
|
+
// (5–15s tick stall every 10 ticks at 50+ worktrees), but unbounded
|
|
263
|
+
// Promise.all would spawn 50+ concurrent git children — bad on Windows
|
|
264
|
+
// where each fork pays AV-scan overhead. Mirrors engine/ado.js:611.
|
|
265
|
+
const BRANCH_PROBE_CONCURRENCY = 5;
|
|
266
|
+
const branchMap = new Map();
|
|
267
|
+
for (let i = 0; i < allDirs.length; i += BRANCH_PROBE_CONCURRENCY) {
|
|
268
|
+
const batch = allDirs.slice(i, i + BRANCH_PROBE_CONCURRENCY);
|
|
269
|
+
const pairs = await Promise.all(
|
|
270
|
+
batch.map(async ({ wtPath }) => [wtPath, await getWorktreeBranchAsync(wtPath)])
|
|
271
|
+
);
|
|
272
|
+
for (const [wtPath, branch] of pairs) branchMap.set(wtPath, branch);
|
|
273
|
+
}
|
|
274
|
+
|
|
251
275
|
for (const { dir, wtPath } of allDirs) {
|
|
252
276
|
|
|
253
277
|
let shouldClean = false;
|
|
254
278
|
let isProtected = false;
|
|
255
|
-
const actualBranch =
|
|
279
|
+
const actualBranch = branchMap.get(wtPath) || '';
|
|
256
280
|
|
|
257
281
|
// Check if this worktree's branch is merged/abandoned
|
|
258
282
|
// Prefer actual git branch metadata; compact Windows dirs intentionally omit branch names.
|
package/engine/cli.js
CHANGED
|
@@ -745,6 +745,16 @@ const commands = {
|
|
|
745
745
|
}
|
|
746
746
|
watchForWorkChanges();
|
|
747
747
|
|
|
748
|
+
// Drain in-memory buffers (metrics, logs) before any process.exit. Logs
|
|
749
|
+
// a stderr line if a flush fails so a refactor mistake (typo, missing
|
|
750
|
+
// export) doesn't go silent.
|
|
751
|
+
function drainBuffers() {
|
|
752
|
+
try { require('./llm').flushMetricsBuffer(); }
|
|
753
|
+
catch (err) { try { console.error(`[shutdown] flushMetricsBuffer failed: ${err.message}`); } catch {} }
|
|
754
|
+
try { shared.flushLogs(); }
|
|
755
|
+
catch (err) { try { console.error(`[shutdown] flushLogs failed: ${err.message}`); } catch {} }
|
|
756
|
+
}
|
|
757
|
+
|
|
748
758
|
// Graceful shutdown — wait for active agents before exiting
|
|
749
759
|
let shuttingDown = false;
|
|
750
760
|
function gracefulShutdown(signal) {
|
|
@@ -768,7 +778,7 @@ const commands = {
|
|
|
768
778
|
e.log('warn', 'Graceful shutdown skipped control.json stopped transition; control file is owned by a different engine process');
|
|
769
779
|
}
|
|
770
780
|
e.log('info', 'Graceful shutdown complete (no active agents)');
|
|
771
|
-
|
|
781
|
+
drainBuffers();
|
|
772
782
|
console.log('No active agents — stopped.');
|
|
773
783
|
process.exit(0);
|
|
774
784
|
}
|
|
@@ -785,7 +795,7 @@ const commands = {
|
|
|
785
795
|
e.log('warn', 'Graceful shutdown skipped control.json stopped transition; control file is owned by a different engine process');
|
|
786
796
|
}
|
|
787
797
|
e.log('info', 'Graceful shutdown complete (all agents finished)');
|
|
788
|
-
|
|
798
|
+
drainBuffers();
|
|
789
799
|
console.log('All agents finished — stopped.');
|
|
790
800
|
process.exit(0);
|
|
791
801
|
}
|
|
@@ -796,7 +806,7 @@ const commands = {
|
|
|
796
806
|
e.log('warn', 'Graceful shutdown skipped control.json stopped transition; control file is owned by a different engine process');
|
|
797
807
|
}
|
|
798
808
|
e.log('warn', `Graceful shutdown timed out after ${timeout / 1000}s with ${e.activeProcesses.size} agent(s) still active`);
|
|
799
|
-
|
|
809
|
+
drainBuffers();
|
|
800
810
|
console.log(`Shutdown timeout (${timeout / 1000}s) — force exiting with ${e.activeProcesses.size} agent(s) still running.`);
|
|
801
811
|
process.exit(1);
|
|
802
812
|
}
|
|
@@ -811,7 +821,7 @@ const commands = {
|
|
|
811
821
|
const msg = reason instanceof Error ? reason.stack || reason.message : String(reason);
|
|
812
822
|
console.error(`[FATAL] Unhandled promise rejection: ${msg}`);
|
|
813
823
|
try { shared.log('fatal', `Unhandled promise rejection: ${msg}`); } catch { /* best effort */ }
|
|
814
|
-
|
|
824
|
+
drainBuffers();
|
|
815
825
|
process.exit(1);
|
|
816
826
|
});
|
|
817
827
|
|
|
@@ -819,7 +829,7 @@ const commands = {
|
|
|
819
829
|
const msg = err instanceof Error ? err.stack || err.message : String(err);
|
|
820
830
|
console.error(`[FATAL] Uncaught exception: ${msg}`);
|
|
821
831
|
try { shared.log('fatal', `Uncaught exception: ${msg}`); } catch { /* best effort */ }
|
|
822
|
-
|
|
832
|
+
drainBuffers();
|
|
823
833
|
process.exit(1);
|
|
824
834
|
});
|
|
825
835
|
},
|
|
@@ -1359,11 +1369,11 @@ const commands = {
|
|
|
1359
1369
|
console.log(`\nDone: ${killed.length} dispatches killed, agents reset.`);
|
|
1360
1370
|
},
|
|
1361
1371
|
|
|
1362
|
-
cleanup() {
|
|
1372
|
+
async cleanup() {
|
|
1363
1373
|
const e = engine();
|
|
1364
1374
|
const config = getConfig();
|
|
1365
1375
|
console.log('\n=== Cleanup ===\n');
|
|
1366
|
-
const result = e.runCleanup(config, true);
|
|
1376
|
+
const result = await e.runCleanup(config, true);
|
|
1367
1377
|
console.log(`\nDone: ${result.tempFiles} temp files, ${result.liveOutputs} live outputs, ${result.worktrees} worktrees, ${result.zombies} zombies cleaned.`);
|
|
1368
1378
|
},
|
|
1369
1379
|
|
package/engine/dispatch.js
CHANGED
|
@@ -24,18 +24,35 @@ function lifecycle() { if (!_lifecycle) _lifecycle = require('./lifecycle'); ret
|
|
|
24
24
|
// ─── Dispatch Mutation ───────────────────────────────────────────────────────
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* Safe to call on every mutation: small prompts are untouched.
|
|
27
|
+
* Walk pending + active dispatch entries and snapshot {id → prompt} for items
|
|
28
|
+
* that have a string prompt. Used to diff before/after a mutation so we only
|
|
29
|
+
* re-check items the mutator actually touched (#1167).
|
|
31
30
|
*/
|
|
32
|
-
function
|
|
31
|
+
function _snapshotPrompts(dispatch) {
|
|
32
|
+
const snap = new Map();
|
|
33
|
+
for (const list of [dispatch.pending, dispatch.active]) {
|
|
34
|
+
if (!Array.isArray(list)) continue;
|
|
35
|
+
for (const item of list) {
|
|
36
|
+
if (item && item.id && typeof item.prompt === 'string') snap.set(item.id, item.prompt);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return snap;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Sidecar oversized prompts only for items the mutator added or modified.
|
|
44
|
+
* Keeps dispatch.json safe from bloat (#1167) without paying O(n) on every
|
|
45
|
+
* unrelated mutation (e.g. status flips, completion-marking).
|
|
46
|
+
*/
|
|
47
|
+
function _sidecarChangedPrompts(dispatch, prevSnap) {
|
|
33
48
|
const threshold = ENGINE_DEFAULTS.maxDispatchPromptBytes;
|
|
34
|
-
const
|
|
35
|
-
for (const list of lists) {
|
|
49
|
+
for (const list of [dispatch.pending, dispatch.active]) {
|
|
36
50
|
if (!Array.isArray(list)) continue;
|
|
37
51
|
for (const item of list) {
|
|
38
|
-
if (item
|
|
52
|
+
if (!item || typeof item.prompt !== 'string') continue;
|
|
53
|
+
const prev = item.id ? prevSnap.get(item.id) : undefined;
|
|
54
|
+
if (prev === item.prompt) continue; // untouched — already validated on insert
|
|
55
|
+
sidecarDispatchPrompt(item, threshold);
|
|
39
56
|
}
|
|
40
57
|
}
|
|
41
58
|
}
|
|
@@ -46,10 +63,11 @@ function mutateDispatch(mutator) {
|
|
|
46
63
|
dispatch.pending = Array.isArray(dispatch.pending) ? dispatch.pending : [];
|
|
47
64
|
dispatch.active = Array.isArray(dispatch.active) ? dispatch.active : [];
|
|
48
65
|
dispatch.completed = Array.isArray(dispatch.completed) ? dispatch.completed : [];
|
|
66
|
+
const prevSnap = _snapshotPrompts(dispatch);
|
|
49
67
|
const next = mutator(dispatch) ?? dispatch;
|
|
50
|
-
// Prompt-size guard:
|
|
51
|
-
//
|
|
52
|
-
|
|
68
|
+
// Prompt-size guard: only scan items whose prompt changed (or new items),
|
|
69
|
+
// so a 100-item status-flip doesn't re-byte-count every prompt.
|
|
70
|
+
_sidecarChangedPrompts(next, prevSnap);
|
|
53
71
|
return next;
|
|
54
72
|
}, { defaultValue: defaultDispatch });
|
|
55
73
|
// Invalidate the read cache so next getDispatch() sees fresh data
|
package/engine/llm.js
CHANGED
|
@@ -26,41 +26,119 @@ const COPILOT_TASK_COMPLETE_GRACE_MS = 3000;
|
|
|
26
26
|
const MISSING_RUNTIME_EXIT_CODE = 78;
|
|
27
27
|
|
|
28
28
|
// ─── Engine-Usage Metrics ────────────────────────────────────────────────────
|
|
29
|
+
//
|
|
30
|
+
// Updates accumulate in an in-memory buffer and flush every
|
|
31
|
+
// metricsFlushIntervalMs (default 10s). Replaces the per-call mutateJsonFileLocked
|
|
32
|
+
// that was both serializing the LLM hot path and bumping metrics.json mtime
|
|
33
|
+
// on every call — defeating the dashboard fast-state mtime-based early-exit.
|
|
34
|
+
|
|
35
|
+
let _pendingMetrics = { engine: Object.create(null), daily: Object.create(null) };
|
|
36
|
+
let _flushTimer = null;
|
|
37
|
+
|
|
38
|
+
function _emptyEngineDelta() {
|
|
39
|
+
return { calls: 0, costUsd: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheCreation: 0, totalDurationMs: 0, timedCalls: 0 };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function _emptyDailyDelta() {
|
|
43
|
+
return { costUsd: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0 };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function _ensureFlushTimer() {
|
|
47
|
+
if (_flushTimer) return;
|
|
48
|
+
const interval = shared.ENGINE_DEFAULTS.metricsFlushIntervalMs || 10000;
|
|
49
|
+
_flushTimer = setInterval(flushMetricsBuffer, interval);
|
|
50
|
+
if (typeof _flushTimer.unref === 'function') _flushTimer.unref();
|
|
51
|
+
}
|
|
29
52
|
|
|
30
53
|
function trackEngineUsage(category, usage) {
|
|
31
54
|
if (!usage) return;
|
|
32
55
|
if (category && (category.startsWith('_test') || category.startsWith('test-'))) return;
|
|
56
|
+
|
|
57
|
+
if (!_pendingMetrics.engine[category]) _pendingMetrics.engine[category] = _emptyEngineDelta();
|
|
58
|
+
const cat = _pendingMetrics.engine[category];
|
|
59
|
+
cat.calls++;
|
|
60
|
+
cat.costUsd += usage.costUsd || 0;
|
|
61
|
+
cat.inputTokens += usage.inputTokens || 0;
|
|
62
|
+
cat.outputTokens += usage.outputTokens || 0;
|
|
63
|
+
cat.cacheRead += usage.cacheRead || 0;
|
|
64
|
+
cat.cacheCreation += usage.cacheCreation || 0;
|
|
65
|
+
if (usage.durationMs) {
|
|
66
|
+
cat.totalDurationMs += usage.durationMs;
|
|
67
|
+
cat.timedCalls += 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const today = ts().slice(0, 10);
|
|
71
|
+
if (!_pendingMetrics.daily[today]) _pendingMetrics.daily[today] = _emptyDailyDelta();
|
|
72
|
+
const daily = _pendingMetrics.daily[today];
|
|
73
|
+
daily.costUsd += usage.costUsd || 0;
|
|
74
|
+
daily.inputTokens += usage.inputTokens || 0;
|
|
75
|
+
daily.outputTokens += usage.outputTokens || 0;
|
|
76
|
+
daily.cacheRead += usage.cacheRead || 0;
|
|
77
|
+
|
|
78
|
+
_ensureFlushTimer();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function flushMetricsBuffer() {
|
|
82
|
+
const pending = _pendingMetrics;
|
|
83
|
+
if (!Object.keys(pending.engine).length && !Object.keys(pending.daily).length) return;
|
|
84
|
+
_pendingMetrics = { engine: Object.create(null), daily: Object.create(null) };
|
|
85
|
+
|
|
33
86
|
try {
|
|
34
87
|
const metricsPath = path.join(ENGINE_DIR, 'metrics.json');
|
|
35
88
|
mutateJsonFileLocked(metricsPath, (metrics) => {
|
|
36
89
|
if (!metrics._engine) metrics._engine = {};
|
|
37
|
-
|
|
38
|
-
metrics._engine[category]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
90
|
+
for (const [category, delta] of Object.entries(pending.engine)) {
|
|
91
|
+
if (!metrics._engine[category]) {
|
|
92
|
+
metrics._engine[category] = { calls: 0, costUsd: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheCreation: 0 };
|
|
93
|
+
}
|
|
94
|
+
const cat = metrics._engine[category];
|
|
95
|
+
cat.calls = (cat.calls || 0) + delta.calls;
|
|
96
|
+
cat.costUsd = (cat.costUsd || 0) + delta.costUsd;
|
|
97
|
+
cat.inputTokens = (cat.inputTokens || 0) + delta.inputTokens;
|
|
98
|
+
cat.outputTokens = (cat.outputTokens || 0) + delta.outputTokens;
|
|
99
|
+
cat.cacheRead = (cat.cacheRead || 0) + delta.cacheRead;
|
|
100
|
+
cat.cacheCreation = (cat.cacheCreation || 0) + delta.cacheCreation;
|
|
101
|
+
if (delta.timedCalls > 0) {
|
|
102
|
+
cat.totalDurationMs = (cat.totalDurationMs || 0) + delta.totalDurationMs;
|
|
103
|
+
cat.timedCalls = (cat.timedCalls || 0) + delta.timedCalls;
|
|
104
|
+
}
|
|
50
105
|
}
|
|
51
|
-
|
|
52
|
-
const today = ts().slice(0, 10);
|
|
53
106
|
if (!metrics._daily) metrics._daily = {};
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
107
|
+
for (const [day, delta] of Object.entries(pending.daily)) {
|
|
108
|
+
if (!metrics._daily[day]) {
|
|
109
|
+
metrics._daily[day] = { costUsd: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, tasks: 0 };
|
|
110
|
+
}
|
|
111
|
+
const d = metrics._daily[day];
|
|
112
|
+
d.costUsd += delta.costUsd;
|
|
113
|
+
d.inputTokens += delta.inputTokens;
|
|
114
|
+
d.outputTokens += delta.outputTokens;
|
|
115
|
+
d.cacheRead += delta.cacheRead;
|
|
116
|
+
}
|
|
61
117
|
return metrics;
|
|
62
118
|
});
|
|
63
|
-
} catch (e) {
|
|
119
|
+
} catch (e) {
|
|
120
|
+
// Re-merge pending so the next flush retries — never drop counters silently.
|
|
121
|
+
for (const [category, delta] of Object.entries(pending.engine)) {
|
|
122
|
+
if (!_pendingMetrics.engine[category]) _pendingMetrics.engine[category] = _emptyEngineDelta();
|
|
123
|
+
const c = _pendingMetrics.engine[category];
|
|
124
|
+
c.calls += delta.calls; c.costUsd += delta.costUsd;
|
|
125
|
+
c.inputTokens += delta.inputTokens; c.outputTokens += delta.outputTokens;
|
|
126
|
+
c.cacheRead += delta.cacheRead; c.cacheCreation += delta.cacheCreation;
|
|
127
|
+
c.totalDurationMs += delta.totalDurationMs; c.timedCalls += delta.timedCalls;
|
|
128
|
+
}
|
|
129
|
+
for (const [day, delta] of Object.entries(pending.daily)) {
|
|
130
|
+
if (!_pendingMetrics.daily[day]) _pendingMetrics.daily[day] = _emptyDailyDelta();
|
|
131
|
+
const d = _pendingMetrics.daily[day];
|
|
132
|
+
d.costUsd += delta.costUsd; d.inputTokens += delta.inputTokens;
|
|
133
|
+
d.outputTokens += delta.outputTokens; d.cacheRead += delta.cacheRead;
|
|
134
|
+
}
|
|
135
|
+
console.error('metrics flush:', e.message);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function _resetMetricsBufferForTest() {
|
|
140
|
+
_pendingMetrics = { engine: Object.create(null), daily: Object.create(null) };
|
|
141
|
+
if (_flushTimer) { clearInterval(_flushTimer); _flushTimer = null; }
|
|
64
142
|
}
|
|
65
143
|
|
|
66
144
|
// ─── Runtime Binary Resolution (TTL-cached) ──────────────────────────────────
|
|
@@ -83,7 +161,10 @@ function _resolveBin(runtime) {
|
|
|
83
161
|
if (!runtime) return null;
|
|
84
162
|
const key = runtime.name;
|
|
85
163
|
const cached = _binCache.get(key);
|
|
86
|
-
|
|
164
|
+
// Trust the 30-min TTL — skip per-call existsSync (10-50ms on Windows w/ AV).
|
|
165
|
+
// If the binary disappears mid-window, spawn fails with ENOENT and the
|
|
166
|
+
// adapter's resolveBinary() reprobes on the next cache miss.
|
|
167
|
+
if (cached && Date.now() - cached.ts < _BIN_TTL) {
|
|
87
168
|
return { bin: cached.bin, native: cached.native, leadingArgs: cached.leadingArgs };
|
|
88
169
|
}
|
|
89
170
|
let resolved = null;
|
|
@@ -700,10 +781,12 @@ module.exports = {
|
|
|
700
781
|
callLLM,
|
|
701
782
|
callLLMStreaming,
|
|
702
783
|
trackEngineUsage,
|
|
784
|
+
flushMetricsBuffer,
|
|
703
785
|
// Exposed for unit tests — engine code MUST use the runtime adapter contract.
|
|
704
786
|
_buildSpawnAgentFlags,
|
|
705
787
|
_resolveBin,
|
|
706
788
|
_resetBinCache,
|
|
789
|
+
_resetMetricsBufferForTest,
|
|
707
790
|
_resolveRuntimeFor,
|
|
708
791
|
_resolveModelFor,
|
|
709
792
|
_resolveModelForRuntime,
|
package/engine/shared.js
CHANGED
|
@@ -918,6 +918,7 @@ const ENGINE_DEFAULTS = {
|
|
|
918
918
|
docSessionTtlMs: 7 * 24 * 60 * 60 * 1000, // 7d — longer-lived doc sessions, still bounded
|
|
919
919
|
docSessionMaxEntries: 200, // cap doc-chat session map/disk store by least-recent activity
|
|
920
920
|
ccLiveStreamMaxAgeMs: 30 * 60 * 1000, // hard cap reconnect buffers if abort/cleanup stalls
|
|
921
|
+
metricsFlushIntervalMs: 10000, // batch trackEngineUsage writes to metrics.json — flushed every 10s instead of per-call to cut lock contention and dashboard mtime churn
|
|
921
922
|
maxLlmRawBytes: 256 * 1024, // keep only a bounded stdout tail from direct Claude calls
|
|
922
923
|
maxLlmStderrBytes: 64 * 1024, // keep only a bounded stderr tail from direct Claude calls
|
|
923
924
|
maxLlmLineBufferBytes: 128 * 1024, // cap the incremental JSON line buffer to avoid malformed-stream OOMs
|
package/engine.js
CHANGED
|
@@ -4071,7 +4071,7 @@ async function tickInner() {
|
|
|
4071
4071
|
|
|
4072
4072
|
// 2.5. Periodic cleanup + MCP sync (every 10 ticks = ~5 minutes)
|
|
4073
4073
|
if (tickCount % 10 === 0) {
|
|
4074
|
-
|
|
4074
|
+
try { await runCleanup(config); } catch (e) { log('warn', `runCleanup: ${e.message}`); }
|
|
4075
4075
|
}
|
|
4076
4076
|
|
|
4077
4077
|
// 2.55. Check persistent watches (3 tick-equivalents, default ~3 minutes)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1737",
|
|
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"
|