@yemi33/minions 0.1.1047 → 0.1.1048
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/dashboard.js +48 -19
- package/engine/cleanup.js +18 -2
- package/engine/llm.js +110 -43
- package/engine/meeting.js +45 -11
- package/engine/pipeline.js +15 -2
- package/engine/playbook.js +23 -10
- package/engine/shared.js +54 -0
- package/engine.js +4 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.1048 (2026-04-17)
|
|
4
4
|
|
|
5
5
|
### Fixes
|
|
6
6
|
- stamp live-output.log stub before spawn (#1198)
|
|
7
7
|
- harden settings save and migrate pr poll config
|
|
8
8
|
|
|
9
9
|
### Other
|
|
10
|
+
- Harden prompt context handling
|
|
10
11
|
- Harden loop watch conversion
|
|
11
12
|
- Add watches sidebar activity badge
|
|
12
13
|
- test(cli): add unit tests for handleCommand, start, stop, kill, spawn (#1191)
|
package/dashboard.js
CHANGED
|
@@ -533,9 +533,9 @@ setInterval(() => {
|
|
|
533
533
|
|
|
534
534
|
// ── Command Center: session state + helpers ─────────────────────────────────
|
|
535
535
|
|
|
536
|
-
//
|
|
537
|
-
|
|
538
|
-
const
|
|
536
|
+
// Bound resumed session growth so stale conversations do not accumulate unbounded context.
|
|
537
|
+
const CC_SESSION_MAX_TURNS = shared.ENGINE_DEFAULTS.ccMaxTurns;
|
|
538
|
+
const CC_SESSION_TTL_MS = shared.ENGINE_DEFAULTS.ccSessionTtlMs;
|
|
539
539
|
let ccSession = { sessionId: null, createdAt: null, lastActiveAt: null, turnCount: 0 };
|
|
540
540
|
const ccInFlightTabs = new Map(); // tabId → timestamp — per-tab in-flight tracking for parallel CC requests
|
|
541
541
|
const ccInFlightAborts = new Map(); // tabId → abortFn — lets a new request kill the stale LLM
|
|
@@ -568,15 +568,14 @@ function ccSessionValid() {
|
|
|
568
568
|
ccSession = { sessionId: null, createdAt: null, lastActiveAt: null, turnCount: 0 };
|
|
569
569
|
return false;
|
|
570
570
|
}
|
|
571
|
+
if (_sessionExpired(ccSession.lastActiveAt || ccSession.createdAt, CC_SESSION_TTL_MS)) {
|
|
572
|
+
console.log('[CC] Session expired by TTL — starting fresh');
|
|
573
|
+
ccSession = { sessionId: null, createdAt: null, lastActiveAt: null, turnCount: 0 };
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
571
576
|
return ccSession.turnCount < CC_SESSION_MAX_TURNS;
|
|
572
577
|
}
|
|
573
578
|
|
|
574
|
-
// Load persisted CC session on startup
|
|
575
|
-
try {
|
|
576
|
-
const saved = safeJson(path.join(ENGINE_DIR, 'cc-session.json'));
|
|
577
|
-
if (saved && saved.sessionId) ccSession = saved;
|
|
578
|
-
} catch { /* optional */ }
|
|
579
|
-
|
|
580
579
|
// Static system prompt — baked into session on creation, never changes
|
|
581
580
|
// Load CC system prompt from file — editable without touching engine code
|
|
582
581
|
const CC_STATIC_SYSTEM_PROMPT = (() => {
|
|
@@ -592,6 +591,34 @@ const CC_STATIC_SYSTEM_PROMPT = (() => {
|
|
|
592
591
|
// Hash the system prompt so we can detect changes and invalidate stale sessions
|
|
593
592
|
const _ccPromptHash = require('crypto').createHash('md5').update(CC_STATIC_SYSTEM_PROMPT).digest('hex').slice(0, 8);
|
|
594
593
|
|
|
594
|
+
function _sessionExpired(lastActiveAt, ttlMs) {
|
|
595
|
+
if (!lastActiveAt || !ttlMs) return false;
|
|
596
|
+
const at = new Date(lastActiveAt).getTime();
|
|
597
|
+
if (!Number.isFinite(at)) return true;
|
|
598
|
+
return Date.now() - at > ttlMs;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function _filterCcTabSessions(sessions) {
|
|
602
|
+
return (Array.isArray(sessions) ? sessions : []).filter(s =>
|
|
603
|
+
s && s.id && s.sessionId &&
|
|
604
|
+
(s.turnCount || 0) < CC_SESSION_MAX_TURNS &&
|
|
605
|
+
!_sessionExpired(s.lastActiveAt || s.createdAt, CC_SESSION_TTL_MS) &&
|
|
606
|
+
(!s._promptHash || s._promptHash === _ccPromptHash)
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function _readCcTabSessions({ prune = true } = {}) {
|
|
611
|
+
const sessions = _filterCcTabSessions(shared.safeJsonArr(CC_SESSIONS_PATH));
|
|
612
|
+
if (prune) safeWrite(CC_SESSIONS_PATH, sessions);
|
|
613
|
+
return sessions;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Load persisted CC session on startup
|
|
617
|
+
try {
|
|
618
|
+
const saved = safeJson(path.join(ENGINE_DIR, 'cc-session.json'));
|
|
619
|
+
if (saved && saved.sessionId && !_sessionExpired(saved.lastActiveAt || saved.createdAt, CC_SESSION_TTL_MS)) ccSession = saved;
|
|
620
|
+
} catch { /* optional */ }
|
|
621
|
+
|
|
595
622
|
let _preambleCache = null;
|
|
596
623
|
let _preambleCacheTs = 0;
|
|
597
624
|
const PREAMBLE_TTL = 30000; // 30s — longer TTL since preamble is lightweight orientation, not real-time data
|
|
@@ -998,17 +1025,16 @@ async function executeCCActions(actions) {
|
|
|
998
1025
|
// Session store for doc modals — keyed by filePath or title, persisted to disk
|
|
999
1026
|
const CC_SESSIONS_PATH = path.join(ENGINE_DIR, 'cc-sessions.json');
|
|
1000
1027
|
const DOC_SESSIONS_PATH = path.join(ENGINE_DIR, 'doc-sessions.json');
|
|
1001
|
-
const DOC_SESSION_TTL_MS =
|
|
1028
|
+
const DOC_SESSION_TTL_MS = shared.ENGINE_DEFAULTS.docSessionTtlMs;
|
|
1002
1029
|
const docSessions = new Map(); // key → { sessionId, lastActiveAt, turnCount }
|
|
1003
1030
|
|
|
1004
1031
|
// Load persisted doc sessions on startup
|
|
1005
1032
|
try {
|
|
1006
1033
|
const saved = safeJson(DOC_SESSIONS_PATH);
|
|
1007
1034
|
if (saved && typeof saved === 'object') {
|
|
1008
|
-
const now = Date.now();
|
|
1009
1035
|
for (const [key, s] of Object.entries(saved)) {
|
|
1010
1036
|
if (s.turnCount >= CC_SESSION_MAX_TURNS) continue;
|
|
1011
|
-
if (s.lastActiveAt
|
|
1037
|
+
if (_sessionExpired(s.lastActiveAt || s.createdAt, DOC_SESSION_TTL_MS)) continue;
|
|
1012
1038
|
docSessions.set(key, s);
|
|
1013
1039
|
}
|
|
1014
1040
|
}
|
|
@@ -1052,7 +1078,7 @@ function resolveSession(store, key) {
|
|
|
1052
1078
|
persistDocSessions();
|
|
1053
1079
|
return null;
|
|
1054
1080
|
}
|
|
1055
|
-
if (s.lastActiveAt
|
|
1081
|
+
if (_sessionExpired(s.lastActiveAt || s.createdAt, DOC_SESSION_TTL_MS)) {
|
|
1056
1082
|
docSessions.delete(key);
|
|
1057
1083
|
persistDocSessions();
|
|
1058
1084
|
return null;
|
|
@@ -3819,14 +3845,14 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3819
3845
|
}
|
|
3820
3846
|
|
|
3821
3847
|
async function handleCCSessionsList(req, res) {
|
|
3822
|
-
const sessions =
|
|
3848
|
+
const sessions = _readCcTabSessions();
|
|
3823
3849
|
return jsonReply(res, 200, { sessions });
|
|
3824
3850
|
}
|
|
3825
3851
|
|
|
3826
3852
|
async function handleCCSessionDelete(req, res, match) {
|
|
3827
3853
|
const id = match?.[1];
|
|
3828
3854
|
if (!id) return jsonReply(res, 400, { error: 'id required' });
|
|
3829
|
-
const sessions =
|
|
3855
|
+
const sessions = _readCcTabSessions();
|
|
3830
3856
|
const filtered = sessions.filter(s => s.id !== id);
|
|
3831
3857
|
safeWrite(CC_SESSIONS_PATH, filtered);
|
|
3832
3858
|
return jsonReply(res, 200, { ok: true });
|
|
@@ -3885,7 +3911,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3885
3911
|
}
|
|
3886
3912
|
|
|
3887
3913
|
const parsed = parseCCActions(result.text);
|
|
3888
|
-
const toolUses = _extractToolUsesFromRaw(result.raw);
|
|
3914
|
+
const toolUses = Array.isArray(result.toolUses) ? result.toolUses : _extractToolUsesFromRaw(result.raw);
|
|
3889
3915
|
// Safety net: detect /loop invocation and convert to create-watch
|
|
3890
3916
|
const _loopWatch = _detectLoopInvocation(parsed.text, parsed.actions, toolUses);
|
|
3891
3917
|
if (_loopWatch) {
|
|
@@ -3981,9 +4007,12 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3981
4007
|
let sessionReset = false;
|
|
3982
4008
|
// If system prompt changed since this session was created, force a fresh session
|
|
3983
4009
|
if (tabSessionId) {
|
|
3984
|
-
const sessions =
|
|
4010
|
+
const sessions = _readCcTabSessions();
|
|
3985
4011
|
const tabEntry = sessions.find(s => s.id === (body.tabId || 'default'));
|
|
3986
|
-
if (tabEntry
|
|
4012
|
+
if (!tabEntry) {
|
|
4013
|
+
tabSessionId = null;
|
|
4014
|
+
sessionReset = true;
|
|
4015
|
+
} else if (tabEntry._promptHash && tabEntry._promptHash !== _ccPromptHash) {
|
|
3987
4016
|
tabSessionId = null;
|
|
3988
4017
|
sessionReset = true;
|
|
3989
4018
|
}
|
|
@@ -4065,7 +4094,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4065
4094
|
const _persistTabId = body.tabId;
|
|
4066
4095
|
if (_persistTabId && responseSessionId) {
|
|
4067
4096
|
try {
|
|
4068
|
-
const sessions =
|
|
4097
|
+
const sessions = _readCcTabSessions();
|
|
4069
4098
|
const existing = sessions.find(s => s.id === _persistTabId);
|
|
4070
4099
|
const preview = (body.message || '').slice(0, 80);
|
|
4071
4100
|
if (existing) {
|
package/engine/cleanup.js
CHANGED
|
@@ -369,6 +369,22 @@ function runCleanup(config, verbose = false) {
|
|
|
369
369
|
} catch (e) { log('warn', 'prune output archives: ' + e.message); }
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
+
// 6b. Prune notes/archive — keep the most recent bounded set
|
|
373
|
+
cleaned.notesArchive = 0;
|
|
374
|
+
try {
|
|
375
|
+
const archiveDir = path.join(MINIONS_DIR, 'notes', 'archive');
|
|
376
|
+
if (fs.existsSync(archiveDir)) {
|
|
377
|
+
const archiveFiles = fs.readdirSync(archiveDir)
|
|
378
|
+
.map(name => ({ name, mtime: fs.statSync(path.join(archiveDir, name)).mtimeMs }))
|
|
379
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
380
|
+
if (archiveFiles.length > ENGINE_DEFAULTS.notesArchiveMaxFiles) {
|
|
381
|
+
for (const old of archiveFiles.slice(ENGINE_DEFAULTS.notesArchiveMaxFiles)) {
|
|
382
|
+
try { fs.unlinkSync(path.join(archiveDir, old.name)); cleaned.notesArchive++; } catch { /* cleanup */ }
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
} catch (e) { log('warn', 'prune notes archive: ' + e.message); }
|
|
387
|
+
|
|
372
388
|
// 7. Prune orphaned dispatch entries — items whose source work item no longer exists
|
|
373
389
|
cleaned.orphanedDispatches = 0;
|
|
374
390
|
try {
|
|
@@ -708,8 +724,8 @@ function runCleanup(config, verbose = false) {
|
|
|
708
724
|
// 14. Scrub stale temp agent keys from metrics.json
|
|
709
725
|
try { scrubStaleMetrics(); } catch { /* best-effort cleanup */ }
|
|
710
726
|
|
|
711
|
-
if (cleaned.ccSessions + cleaned.docSessions + cleaned.cooldowns + cleaned.pidFiles + cleaned.pendingContextsTrimmed > 0) {
|
|
712
|
-
log('info', `Cleanup (resources): ${cleaned.ccSessions} cc-sessions, ${cleaned.docSessions} doc-sessions, ${cleaned.cooldowns} cooldowns, ${cleaned.pendingContextsTrimmed} pendingCtx trimmed, ${cleaned.pidFiles} PID files`);
|
|
727
|
+
if (cleaned.ccSessions + cleaned.docSessions + cleaned.cooldowns + cleaned.pidFiles + cleaned.pendingContextsTrimmed + cleaned.notesArchive > 0) {
|
|
728
|
+
log('info', `Cleanup (resources): ${cleaned.ccSessions} cc-sessions, ${cleaned.docSessions} doc-sessions, ${cleaned.cooldowns} cooldowns, ${cleaned.pendingContextsTrimmed} pendingCtx trimmed, ${cleaned.notesArchive} archived notes, ${cleaned.pidFiles} PID files`);
|
|
713
729
|
}
|
|
714
730
|
|
|
715
731
|
return cleaned;
|
package/engine/llm.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const shared = require('./shared');
|
|
8
|
-
const { safeWrite, safeUnlink, uid, ts, runFile, cleanChildEnv, parseStreamJsonOutput, mutateJsonFileLocked } = shared;
|
|
8
|
+
const { safeWrite, safeUnlink, uid, ts, runFile, cleanChildEnv, parseStreamJsonOutput, mutateJsonFileLocked, appendTextTail, ENGINE_DEFAULTS } = shared;
|
|
9
9
|
|
|
10
10
|
const MINIONS_DIR = shared.MINIONS_DIR;
|
|
11
11
|
const ENGINE_DIR = path.join(MINIONS_DIR, 'engine');
|
|
@@ -127,6 +127,93 @@ function _spawnProcess(promptText, sysPromptText, { direct, label, model, maxTur
|
|
|
127
127
|
return { proc, cleanupFiles };
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
function _createStreamAccumulator({
|
|
131
|
+
maxRawBytes,
|
|
132
|
+
maxStderrBytes,
|
|
133
|
+
maxLineBufferBytes,
|
|
134
|
+
maxTextLength = 0,
|
|
135
|
+
onChunk = null,
|
|
136
|
+
onToolUse = null,
|
|
137
|
+
}) {
|
|
138
|
+
let stdout = '';
|
|
139
|
+
let stderr = '';
|
|
140
|
+
let lineBuf = '';
|
|
141
|
+
let text = '';
|
|
142
|
+
let usage = null;
|
|
143
|
+
let sessionId = null;
|
|
144
|
+
let lastTextSent = '';
|
|
145
|
+
const toolUses = [];
|
|
146
|
+
|
|
147
|
+
function captureResult(obj) {
|
|
148
|
+
if (!obj || typeof obj !== 'object') return;
|
|
149
|
+
if (obj.session_id) sessionId = obj.session_id;
|
|
150
|
+
if (obj.type === 'result') {
|
|
151
|
+
if (typeof obj.result === 'string') {
|
|
152
|
+
text = maxTextLength ? obj.result.slice(0, maxTextLength) : obj.result;
|
|
153
|
+
}
|
|
154
|
+
if (obj.total_cost_usd || obj.usage) {
|
|
155
|
+
usage = {
|
|
156
|
+
costUsd: obj.total_cost_usd || 0,
|
|
157
|
+
inputTokens: obj.usage?.input_tokens || 0,
|
|
158
|
+
outputTokens: obj.usage?.output_tokens || 0,
|
|
159
|
+
cacheRead: obj.usage?.cache_read_input_tokens || obj.usage?.cacheReadInputTokens || 0,
|
|
160
|
+
cacheCreation: obj.usage?.cache_creation_input_tokens || obj.usage?.cacheCreationInputTokens || 0,
|
|
161
|
+
durationMs: obj.duration_ms || 0,
|
|
162
|
+
numTurns: obj.num_turns || 0,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (obj.type === 'assistant' && Array.isArray(obj.message?.content)) {
|
|
167
|
+
for (const block of obj.message.content) {
|
|
168
|
+
if (block?.type === 'text' && block.text) {
|
|
169
|
+
text = maxTextLength ? block.text.slice(0, maxTextLength) : block.text;
|
|
170
|
+
if (onChunk && block.text !== lastTextSent) {
|
|
171
|
+
lastTextSent = block.text;
|
|
172
|
+
onChunk(block.text);
|
|
173
|
+
}
|
|
174
|
+
} else if (block?.type === 'tool_use' && block.name) {
|
|
175
|
+
const toolUse = { name: block.name, input: block.input || {} };
|
|
176
|
+
toolUses.push(toolUse);
|
|
177
|
+
if (onToolUse) onToolUse(toolUse.name, toolUse.input);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function ingestStdout(chunk) {
|
|
184
|
+
const str = chunk == null ? '' : chunk.toString();
|
|
185
|
+
stdout = appendTextTail(stdout, str, maxRawBytes, '...(truncated stdout)\n');
|
|
186
|
+
lineBuf = appendTextTail(lineBuf, str, maxLineBufferBytes, '');
|
|
187
|
+
const lines = lineBuf.split('\n');
|
|
188
|
+
lineBuf = lines.pop() || '';
|
|
189
|
+
for (const line of lines) {
|
|
190
|
+
const trimmed = line.trim();
|
|
191
|
+
if (!trimmed || !trimmed.startsWith('{')) continue;
|
|
192
|
+
try { captureResult(JSON.parse(trimmed)); } catch { /* incomplete JSON or non-JSON line */ }
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function ingestStderr(chunk) {
|
|
197
|
+
stderr = appendTextTail(stderr, chunk == null ? '' : chunk.toString(), maxStderrBytes, '...(truncated stderr)\n');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function finalize() {
|
|
201
|
+
const trimmed = lineBuf.trim();
|
|
202
|
+
if (trimmed.startsWith('{')) {
|
|
203
|
+
try { captureResult(JSON.parse(trimmed)); } catch { /* incomplete trailing JSON */ }
|
|
204
|
+
}
|
|
205
|
+
if (!text || !usage || !sessionId) {
|
|
206
|
+
const parsedTail = parseStreamJsonOutput(stdout, maxTextLength ? { maxTextLength } : {});
|
|
207
|
+
if (!text && parsedTail.text) text = parsedTail.text;
|
|
208
|
+
if (!usage && parsedTail.usage) usage = parsedTail.usage;
|
|
209
|
+
if (!sessionId && parsedTail.sessionId) sessionId = parsedTail.sessionId;
|
|
210
|
+
}
|
|
211
|
+
return { text, usage, sessionId, raw: stdout, stderr, toolUses };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return { ingestStdout, ingestStderr, finalize };
|
|
215
|
+
}
|
|
216
|
+
|
|
130
217
|
// ── Core LLM Call ───────────────────────────────────────────────────────────
|
|
131
218
|
|
|
132
219
|
function callLLM(promptText, sysPromptText, { timeout = 120000, label = 'llm', model = 'sonnet', maxTurns = 1, allowedTools = '', sessionId = null, effort = null, direct = false } = {}) {
|
|
@@ -134,29 +221,32 @@ function callLLM(promptText, sysPromptText, { timeout = 120000, label = 'llm', m
|
|
|
134
221
|
const promise = new Promise((resolve) => {
|
|
135
222
|
const _startMs = Date.now();
|
|
136
223
|
const { proc, cleanupFiles } = _spawnProcess(promptText, sysPromptText, { direct, label, model, maxTurns, allowedTools, effort, sessionId });
|
|
224
|
+
const acc = _createStreamAccumulator({
|
|
225
|
+
maxRawBytes: ENGINE_DEFAULTS.maxLlmRawBytes,
|
|
226
|
+
maxStderrBytes: ENGINE_DEFAULTS.maxLlmStderrBytes,
|
|
227
|
+
maxLineBufferBytes: ENGINE_DEFAULTS.maxLlmLineBufferBytes,
|
|
228
|
+
});
|
|
137
229
|
|
|
138
230
|
_abort = () => { shared.killImmediate(proc); };
|
|
139
231
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
proc.stdout.on('data', d => { stdout += d.toString(); });
|
|
143
|
-
proc.stderr.on('data', d => { stderr += d.toString(); });
|
|
232
|
+
proc.stdout.on('data', d => { acc.ingestStdout(d); });
|
|
233
|
+
proc.stderr.on('data', d => { acc.ingestStderr(d); });
|
|
144
234
|
|
|
145
235
|
const timer = setTimeout(() => { shared.killImmediate(proc); }, timeout);
|
|
146
236
|
|
|
147
237
|
proc.on('close', (code) => {
|
|
148
238
|
clearTimeout(timer);
|
|
149
239
|
for (const f of cleanupFiles) safeUnlink(f);
|
|
150
|
-
const parsed =
|
|
240
|
+
const parsed = acc.finalize();
|
|
151
241
|
const durationMs = Date.now() - _startMs;
|
|
152
242
|
const usage = parsed.usage ? { ...parsed.usage, durationMs } : { durationMs };
|
|
153
|
-
resolve({ text: parsed.text || '', usage, sessionId: parsed.sessionId || null, code, stderr, raw:
|
|
243
|
+
resolve({ text: parsed.text || '', usage, sessionId: parsed.sessionId || null, code, stderr: parsed.stderr, raw: parsed.raw, toolUses: parsed.toolUses });
|
|
154
244
|
});
|
|
155
245
|
|
|
156
246
|
proc.on('error', (err) => {
|
|
157
247
|
clearTimeout(timer);
|
|
158
248
|
for (const f of cleanupFiles) safeUnlink(f);
|
|
159
|
-
resolve({ text: '', usage: null, sessionId: null, code: 1, stderr: err.message, raw: '' });
|
|
249
|
+
resolve({ text: '', usage: null, sessionId: null, code: 1, stderr: err.message, raw: '', toolUses: [] });
|
|
160
250
|
});
|
|
161
251
|
});
|
|
162
252
|
promise.abort = () => { if (_abort) _abort(); };
|
|
@@ -190,56 +280,34 @@ function callLLMStreaming(promptText, sysPromptText, { timeout = 120000, label =
|
|
|
190
280
|
const promise = new Promise((resolve) => {
|
|
191
281
|
const _startMs = Date.now();
|
|
192
282
|
const { proc, cleanupFiles } = _spawnProcess(promptText, sysPromptText, { direct, label, model, maxTurns, allowedTools, effort, sessionId });
|
|
283
|
+
const acc = _createStreamAccumulator({
|
|
284
|
+
maxRawBytes: ENGINE_DEFAULTS.maxLlmRawBytes,
|
|
285
|
+
maxStderrBytes: ENGINE_DEFAULTS.maxLlmStderrBytes,
|
|
286
|
+
maxLineBufferBytes: ENGINE_DEFAULTS.maxLlmLineBufferBytes,
|
|
287
|
+
onChunk,
|
|
288
|
+
onToolUse,
|
|
289
|
+
});
|
|
193
290
|
|
|
194
291
|
_abort = () => { shared.killImmediate(proc); };
|
|
195
292
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
let lineBuf = '';
|
|
199
|
-
|
|
200
|
-
let lastTextSent = '';
|
|
201
|
-
proc.stdout.on('data', d => {
|
|
202
|
-
const chunk = d.toString();
|
|
203
|
-
stdout += chunk;
|
|
204
|
-
lineBuf += chunk;
|
|
205
|
-
// Parse complete lines for streaming text
|
|
206
|
-
const lines = lineBuf.split('\n');
|
|
207
|
-
lineBuf = lines.pop(); // keep incomplete line in buffer
|
|
208
|
-
for (const line of lines) {
|
|
209
|
-
const trimmed = line.trim();
|
|
210
|
-
if (!trimmed || !trimmed.startsWith('{')) continue;
|
|
211
|
-
try {
|
|
212
|
-
const obj = JSON.parse(trimmed);
|
|
213
|
-
if (obj.type === 'assistant' && obj.message?.content) {
|
|
214
|
-
for (const block of obj.message.content) {
|
|
215
|
-
if (block.type === 'text' && block.text && block.text !== lastTextSent) {
|
|
216
|
-
lastTextSent = block.text;
|
|
217
|
-
onChunk(block.text);
|
|
218
|
-
} else if (block.type === 'tool_use' && block.name && onToolUse) {
|
|
219
|
-
onToolUse(block.name, block.input);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
} catch { /* incomplete JSON or non-JSON line */ }
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
proc.stderr.on('data', d => { stderr += d.toString(); });
|
|
293
|
+
proc.stdout.on('data', d => { acc.ingestStdout(d); });
|
|
294
|
+
proc.stderr.on('data', d => { acc.ingestStderr(d); });
|
|
227
295
|
|
|
228
296
|
const timer = setTimeout(() => { shared.killImmediate(proc); }, timeout);
|
|
229
297
|
|
|
230
298
|
proc.on('close', (code) => {
|
|
231
299
|
clearTimeout(timer);
|
|
232
300
|
for (const f of cleanupFiles) safeUnlink(f);
|
|
233
|
-
const parsed =
|
|
301
|
+
const parsed = acc.finalize();
|
|
234
302
|
const durationMs = Date.now() - _startMs;
|
|
235
303
|
const usage = parsed.usage ? { ...parsed.usage, durationMs } : { durationMs };
|
|
236
|
-
resolve({ text: parsed.text || '', usage, sessionId: parsed.sessionId || null, code, stderr, raw:
|
|
304
|
+
resolve({ text: parsed.text || '', usage, sessionId: parsed.sessionId || null, code, stderr: parsed.stderr, raw: parsed.raw, toolUses: parsed.toolUses });
|
|
237
305
|
});
|
|
238
306
|
|
|
239
307
|
proc.on('error', (err) => {
|
|
240
308
|
clearTimeout(timer);
|
|
241
309
|
for (const f of cleanupFiles) safeUnlink(f);
|
|
242
|
-
resolve({ text: '', usage: null, sessionId: null, code: 1, stderr: err.message, raw: '' });
|
|
310
|
+
resolve({ text: '', usage: null, sessionId: null, code: 1, stderr: err.message, raw: '', toolUses: [] });
|
|
243
311
|
});
|
|
244
312
|
});
|
|
245
313
|
promise.abort = () => { if (_abort) _abort(); };
|
|
@@ -252,4 +320,3 @@ module.exports = {
|
|
|
252
320
|
trackEngineUsage,
|
|
253
321
|
isResumeSessionStillValid,
|
|
254
322
|
};
|
|
255
|
-
|
package/engine/meeting.js
CHANGED
|
@@ -18,6 +18,20 @@ const EMPTY_OUTPUT_PATTERNS = ['(no output)', '(no findings)', '(no response)'];
|
|
|
18
18
|
|
|
19
19
|
const MEETINGS_DIR = path.join(__dirname, '..', 'meetings');
|
|
20
20
|
|
|
21
|
+
function truncateMeetingContext(text, maxBytes, label) {
|
|
22
|
+
return shared.truncateTextBytes(text, maxBytes, `\n\n_...${label} truncated — review the meeting transcript if needed._`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatMeetingContributions(entries, agents, emptyText, label, maxBytes) {
|
|
26
|
+
const pairs = Object.entries(typeof entries === 'object' && entries ? entries : {});
|
|
27
|
+
if (pairs.length === 0) return emptyText;
|
|
28
|
+
const perEntryBytes = Math.max(1024, Math.floor(maxBytes / Math.max(1, pairs.length)));
|
|
29
|
+
const combined = pairs.map(([agent, value]) =>
|
|
30
|
+
`### ${agents[agent]?.name || agent}\n\n${truncateMeetingContext(value?.content || emptyText, perEntryBytes, `${label} entry`)}`
|
|
31
|
+
).join('\n\n---\n\n');
|
|
32
|
+
return truncateMeetingContext(combined, maxBytes, label);
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
function getMeetings() {
|
|
22
36
|
if (!fs.existsSync(MEETINGS_DIR)) return [];
|
|
23
37
|
return fs.readdirSync(MEETINGS_DIR)
|
|
@@ -100,13 +114,25 @@ function discoverMeetingWork(config) {
|
|
|
100
114
|
const key = `${concludePrefix}${concluder}`;
|
|
101
115
|
if (activeKeys.has(key)) continue;
|
|
102
116
|
|
|
103
|
-
const humanNotes = (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
117
|
+
const humanNotes = truncateMeetingContext(
|
|
118
|
+
(Array.isArray(meeting.humanNotes) ? meeting.humanNotes : []).map(n => '- ' + n).join('\n'),
|
|
119
|
+
ENGINE_DEFAULTS.maxMeetingHumanNotesBytes,
|
|
120
|
+
'human meeting notes'
|
|
121
|
+
);
|
|
122
|
+
const allFindings = formatMeetingContributions(
|
|
123
|
+
meeting.findings,
|
|
124
|
+
agents,
|
|
125
|
+
'(no findings)',
|
|
126
|
+
'meeting findings',
|
|
127
|
+
ENGINE_DEFAULTS.maxMeetingPromptBytes
|
|
128
|
+
);
|
|
129
|
+
const allDebate = formatMeetingContributions(
|
|
130
|
+
meeting.debate,
|
|
131
|
+
agents,
|
|
132
|
+
'(no response)',
|
|
133
|
+
'meeting debate',
|
|
134
|
+
ENGINE_DEFAULTS.maxMeetingPromptBytes
|
|
135
|
+
);
|
|
110
136
|
|
|
111
137
|
const vars = {
|
|
112
138
|
agent_name: agents[concluder]?.name || concluder,
|
|
@@ -149,7 +175,11 @@ function discoverMeetingWork(config) {
|
|
|
149
175
|
const key = `meeting-${meeting.id}-r${round}-${agentId}`;
|
|
150
176
|
if (activeKeys.has(key)) continue;
|
|
151
177
|
|
|
152
|
-
const humanNotes = (
|
|
178
|
+
const humanNotes = truncateMeetingContext(
|
|
179
|
+
(meeting.humanNotes || []).map(n => '- ' + n).join('\n'),
|
|
180
|
+
ENGINE_DEFAULTS.maxMeetingHumanNotesBytes,
|
|
181
|
+
'human meeting notes'
|
|
182
|
+
);
|
|
153
183
|
const vars = {
|
|
154
184
|
agent_name: agents[agentId]?.name || agentId,
|
|
155
185
|
agent_role: agents[agentId]?.role || 'Agent',
|
|
@@ -160,9 +190,13 @@ function discoverMeetingWork(config) {
|
|
|
160
190
|
};
|
|
161
191
|
|
|
162
192
|
if (roundName === 'debating') {
|
|
163
|
-
vars.all_findings =
|
|
164
|
-
|
|
165
|
-
|
|
193
|
+
vars.all_findings = formatMeetingContributions(
|
|
194
|
+
meeting.findings,
|
|
195
|
+
agents,
|
|
196
|
+
'(no findings)',
|
|
197
|
+
'meeting findings',
|
|
198
|
+
ENGINE_DEFAULTS.maxMeetingPromptBytes
|
|
199
|
+
);
|
|
166
200
|
}
|
|
167
201
|
|
|
168
202
|
const playbookName = roundName === 'investigating' ? 'meeting-investigate' : 'meeting-debate';
|
package/engine/pipeline.js
CHANGED
|
@@ -15,6 +15,10 @@ const { parseCronExpr, shouldRunNow } = require('./scheduler');
|
|
|
15
15
|
const PIPELINES_DIR = path.join(__dirname, '..', 'pipelines');
|
|
16
16
|
const PIPELINE_RUNS_PATH = path.join(__dirname, 'pipeline-runs.json');
|
|
17
17
|
|
|
18
|
+
function truncatePipelineContext(text, maxBytes, label) {
|
|
19
|
+
return shared.truncateTextBytes(text, maxBytes, `\n\n_...${label} truncated — inspect the upstream artifacts if needed._`);
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
// ── Pipeline CRUD ────────────────────────────────────────────────────────────
|
|
19
23
|
|
|
20
24
|
function getPipelines() {
|
|
@@ -433,7 +437,11 @@ async function executePlanStage(stage, stageState, run, config) {
|
|
|
433
437
|
try {
|
|
434
438
|
const mtg = safeJson(path.join(__dirname, '..', 'meetings', mid + '.json'));
|
|
435
439
|
if (mtg) {
|
|
436
|
-
const transcript = (
|
|
440
|
+
const transcript = truncatePipelineContext(
|
|
441
|
+
(mtg.transcript || []).map(formatTranscriptEntry).join('\n\n---\n\n'),
|
|
442
|
+
ENGINE_DEFAULTS.maxPipelineMeetingContextBytes,
|
|
443
|
+
`meeting transcript for ${mtg.title || mid}`
|
|
444
|
+
);
|
|
437
445
|
meetingContext += '# Meeting: ' + (mtg.title || mid) + '\n\n**Agenda:** ' + (mtg.agenda || '') + '\n\n' + transcript + '\n\n';
|
|
438
446
|
}
|
|
439
447
|
} catch (e) { log('warn', `Pipeline plan: failed to read meeting ${mid}: ${e.message}`); }
|
|
@@ -443,10 +451,15 @@ async function executePlanStage(stage, stageState, run, config) {
|
|
|
443
451
|
for (const depId of stage.dependsOn) {
|
|
444
452
|
const depStage = run.stages[depId];
|
|
445
453
|
if (depStage?.output && !depStage.artifacts?.meetings?.length) {
|
|
446
|
-
meetingContext += '## From: ' + depId + '\n\n' +
|
|
454
|
+
meetingContext += '## From: ' + depId + '\n\n' + truncatePipelineContext(
|
|
455
|
+
depStage.output,
|
|
456
|
+
ENGINE_DEFAULTS.maxPipelineMeetingContextBytes,
|
|
457
|
+
`pipeline stage output from ${depId}`
|
|
458
|
+
) + '\n\n';
|
|
447
459
|
}
|
|
448
460
|
}
|
|
449
461
|
}
|
|
462
|
+
meetingContext = truncatePipelineContext(meetingContext, ENGINE_DEFAULTS.maxPipelineMeetingContextBytes, 'pipeline meeting context');
|
|
450
463
|
|
|
451
464
|
// Use LLM to generate a structured plan (same approach as dashboard "Create Plan from Meeting" button)
|
|
452
465
|
let content = '';
|
package/engine/playbook.js
CHANGED
|
@@ -9,7 +9,7 @@ const path = require('path');
|
|
|
9
9
|
const shared = require('./shared');
|
|
10
10
|
const queries = require('./queries');
|
|
11
11
|
|
|
12
|
-
const { safeJson, safeRead, getProjects, log, ts, dateStamp, WI_STATUS, WORK_TYPE, PR_STATUS, DISPATCH_RESULT } = shared;
|
|
12
|
+
const { safeJson, safeRead, getProjects, log, ts, dateStamp, truncateTextBytes, ENGINE_DEFAULTS, WI_STATUS, WORK_TYPE, PR_STATUS, DISPATCH_RESULT } = shared;
|
|
13
13
|
const { getConfig, getDispatch, getNotes, getAgentCharter, getPrs, AGENTS_DIR } = queries;
|
|
14
14
|
|
|
15
15
|
const MINIONS_DIR = path.resolve(__dirname, '..');
|
|
@@ -119,6 +119,10 @@ function resolveTaskContext(item, config) {
|
|
|
119
119
|
}));
|
|
120
120
|
const resolved = { additionalContext: '', referencedFiles: [] };
|
|
121
121
|
|
|
122
|
+
function truncateReferencedContext(content, maxBytes, label) {
|
|
123
|
+
return truncateTextBytes(content, maxBytes, `\n\n_...${label} truncated — read the full file if needed._`);
|
|
124
|
+
}
|
|
125
|
+
|
|
122
126
|
|
|
123
127
|
// Match agent references: "ripley's plan", "dallas's pr", "lambert's output", etc.
|
|
124
128
|
for (const agent of agentNames) {
|
|
@@ -144,7 +148,7 @@ function resolveTaskContext(item, config) {
|
|
|
144
148
|
const planPath = path.join(MINIONS_DIR, 'plans', planFile);
|
|
145
149
|
try {
|
|
146
150
|
const content = safeRead(planPath);
|
|
147
|
-
resolved.additionalContext += `\n\n## Referenced Plan: ${planFile} (created by ${agent.name})\n\n${content}`;
|
|
151
|
+
resolved.additionalContext += `\n\n## Referenced Plan: ${planFile} (created by ${agent.name})\n\n${truncateReferencedContext(content, ENGINE_DEFAULTS.maxReferencedPlanBytes, 'referenced plan')}`;
|
|
148
152
|
resolved.referencedFiles.push(planPath);
|
|
149
153
|
log('info', `Context resolution: found plan "${planFile}" by ${agent.name} for work item ${item.id}`);
|
|
150
154
|
} catch (e) { log('warn', 'resolve plan context: ' + e.message); }
|
|
@@ -155,7 +159,7 @@ function resolveTaskContext(item, config) {
|
|
|
155
159
|
const planPath = path.join(MINIONS_DIR, 'plans', match);
|
|
156
160
|
try {
|
|
157
161
|
const content = safeRead(planPath);
|
|
158
|
-
resolved.additionalContext += `\n\n## Referenced Plan: ${match}\n\n${content}`;
|
|
162
|
+
resolved.additionalContext += `\n\n## Referenced Plan: ${match}\n\n${truncateReferencedContext(content, ENGINE_DEFAULTS.maxReferencedPlanBytes, 'referenced plan')}`;
|
|
159
163
|
resolved.referencedFiles.push(planPath);
|
|
160
164
|
log('info', `Context resolution: found plan "${match}" (name match) for work item ${item.id}`);
|
|
161
165
|
} catch (e) { log('warn', 'resolve plan fallback context: ' + e.message); }
|
|
@@ -178,7 +182,7 @@ function resolveTaskContext(item, config) {
|
|
|
178
182
|
.sort().reverse();
|
|
179
183
|
if (files.length > 0) {
|
|
180
184
|
const content = safeRead(path.join(inboxDir, files[0]));
|
|
181
|
-
resolved.additionalContext += `\n\n## Referenced Notes by ${agent.name}: ${files[0]}\n\n${content.
|
|
185
|
+
resolved.additionalContext += `\n\n## Referenced Notes by ${agent.name}: ${files[0]}\n\n${truncateReferencedContext(content, ENGINE_DEFAULTS.maxReferencedNotesBytes, 'referenced notes')}`;
|
|
182
186
|
resolved.referencedFiles.push(path.join(inboxDir, files[0]));
|
|
183
187
|
log('info', `Context resolution: found notes "${files[0]}" by ${agent.name} for work item ${item.id}`);
|
|
184
188
|
}
|
|
@@ -197,13 +201,20 @@ function resolveTaskContext(item, config) {
|
|
|
197
201
|
if (plans.length > 0) {
|
|
198
202
|
const planPath = path.join(MINIONS_DIR, 'plans', plans[0]);
|
|
199
203
|
const content = safeRead(planPath);
|
|
200
|
-
resolved.additionalContext += `\n\n## Referenced Plan (latest): ${plans[0]}\n\n${content}`;
|
|
204
|
+
resolved.additionalContext += `\n\n## Referenced Plan (latest): ${plans[0]}\n\n${truncateReferencedContext(content, ENGINE_DEFAULTS.maxReferencedPlanBytes, 'referenced plan')}`;
|
|
201
205
|
resolved.referencedFiles.push(planPath);
|
|
202
206
|
log('info', `Context resolution: using latest plan "${plans[0]}" for work item ${item.id}`);
|
|
203
207
|
}
|
|
204
208
|
} catch (e) { log('warn', 'resolve latest plan context: ' + e.message); }
|
|
205
209
|
}
|
|
206
210
|
|
|
211
|
+
if (resolved.additionalContext) {
|
|
212
|
+
resolved.additionalContext = truncateTextBytes(
|
|
213
|
+
resolved.additionalContext,
|
|
214
|
+
ENGINE_DEFAULTS.maxResolvedTaskContextBytes,
|
|
215
|
+
'\n\n_...additional referenced context truncated — read the referenced files if needed._'
|
|
216
|
+
);
|
|
217
|
+
}
|
|
207
218
|
return resolved;
|
|
208
219
|
}
|
|
209
220
|
|
|
@@ -316,14 +327,16 @@ function renderPlaybook(type, vars) {
|
|
|
316
327
|
content += '\n\n---\n\n## Pinned Context (CRITICAL — READ FIRST)\n\n' + pinnedContent;
|
|
317
328
|
}
|
|
318
329
|
|
|
319
|
-
// Inject team notes (single injection point — not in buildAgentContext) — capped
|
|
330
|
+
// Inject team notes (single injection point — not in buildAgentContext) — capped via ENGINE_DEFAULTS
|
|
320
331
|
let notes = getNotes();
|
|
321
332
|
if (notes) {
|
|
322
|
-
if (notes
|
|
333
|
+
if (Buffer.byteLength(notes, 'utf8') > ENGINE_DEFAULTS.maxNotesPromptBytes) {
|
|
323
334
|
const sections = notes.split(/(?=^### )/m);
|
|
324
|
-
const recent = sections.slice(-10).join('');
|
|
325
|
-
|
|
326
|
-
|
|
335
|
+
const recent = sections.slice(-10).join('') || notes;
|
|
336
|
+
const olderCount = Math.max(0, sections.length - 10);
|
|
337
|
+
const footer = olderCount > 0 ? `\n\n_${olderCount} older entries in \`notes.md\` — Read if needed._` : '';
|
|
338
|
+
const budget = Math.max(0, ENGINE_DEFAULTS.maxNotesPromptBytes - Buffer.byteLength(footer, 'utf8'));
|
|
339
|
+
notes = truncateTextBytes(recent, budget, '\n\n_...notes truncated_') + footer;
|
|
327
340
|
}
|
|
328
341
|
content += '\n\n---\n\n## Team Notes (MUST READ)\n\n' + notes;
|
|
329
342
|
}
|
package/engine/shared.js
CHANGED
|
@@ -391,6 +391,44 @@ function uniquePath(filePath) {
|
|
|
391
391
|
return `${base}-${Date.now()}${ext}`;
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
+
function truncateTextBytes(text, maxBytes, suffix = '') {
|
|
395
|
+
const value = text == null ? '' : String(text);
|
|
396
|
+
if (!maxBytes || maxBytes <= 0) return '';
|
|
397
|
+
if (Buffer.byteLength(value, 'utf8') <= maxBytes) return value;
|
|
398
|
+
const suffixText = suffix == null ? '' : String(suffix);
|
|
399
|
+
const suffixBytes = Buffer.byteLength(suffixText, 'utf8');
|
|
400
|
+
const targetBytes = Math.max(0, maxBytes - suffixBytes);
|
|
401
|
+
let low = 0;
|
|
402
|
+
let high = value.length;
|
|
403
|
+
while (low < high) {
|
|
404
|
+
const mid = Math.ceil((low + high) / 2);
|
|
405
|
+
if (Buffer.byteLength(value.slice(0, mid), 'utf8') <= targetBytes) low = mid;
|
|
406
|
+
else high = mid - 1;
|
|
407
|
+
}
|
|
408
|
+
return value.slice(0, low) + suffixText;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function tailTextBytes(text, maxBytes, prefix = '') {
|
|
412
|
+
const value = text == null ? '' : String(text);
|
|
413
|
+
if (!maxBytes || maxBytes <= 0) return '';
|
|
414
|
+
if (Buffer.byteLength(value, 'utf8') <= maxBytes) return value;
|
|
415
|
+
const prefixText = prefix == null ? '' : String(prefix);
|
|
416
|
+
const prefixBytes = Buffer.byteLength(prefixText, 'utf8');
|
|
417
|
+
const targetBytes = Math.max(0, maxBytes - prefixBytes);
|
|
418
|
+
let low = 0;
|
|
419
|
+
let high = value.length;
|
|
420
|
+
while (low < high) {
|
|
421
|
+
const mid = Math.floor((low + high) / 2);
|
|
422
|
+
if (Buffer.byteLength(value.slice(mid), 'utf8') <= targetBytes) high = mid;
|
|
423
|
+
else low = mid + 1;
|
|
424
|
+
}
|
|
425
|
+
return prefixText + value.slice(low);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function appendTextTail(existing, addition, maxBytes, prefix = '') {
|
|
429
|
+
return tailTextBytes((existing || '') + (addition || ''), maxBytes, prefix);
|
|
430
|
+
}
|
|
431
|
+
|
|
394
432
|
// ── Inbox Helpers ───────────────────────────────────────────────────────────
|
|
395
433
|
|
|
396
434
|
/**
|
|
@@ -687,6 +725,19 @@ const ENGINE_DEFAULTS = {
|
|
|
687
725
|
maxDispatchPromptBytes: 1024 * 1024, // 1 MB — dispatch items with prompts larger than this sidecar to engine/contexts/ to prevent dispatch.json OOM (#1167)
|
|
688
726
|
maxStateFileBytes: 100 * 1024 * 1024, // 100 MB — fail startup with a clear error when dispatch.json / cooldowns.json exceed this, rather than silently OOMing on JSON.parse (#1167)
|
|
689
727
|
ccMaxTurns: 50, // max tool-use turns for CC/doc-chat before CLI stops
|
|
728
|
+
ccSessionTtlMs: 2 * 60 * 60 * 1000, // 2h — expire stale resumed CC sessions to cap context growth
|
|
729
|
+
docSessionTtlMs: 7 * 24 * 60 * 60 * 1000, // 7d — longer-lived doc sessions, still bounded
|
|
730
|
+
maxLlmRawBytes: 256 * 1024, // keep only a bounded stdout tail from direct Claude calls
|
|
731
|
+
maxLlmStderrBytes: 64 * 1024, // keep only a bounded stderr tail from direct Claude calls
|
|
732
|
+
maxLlmLineBufferBytes: 128 * 1024, // cap the incremental JSON line buffer to avoid malformed-stream OOMs
|
|
733
|
+
maxReferencedPlanBytes: 12 * 1024, // cap full plan injection when implicit task context references prior plans
|
|
734
|
+
maxReferencedNotesBytes: 5 * 1024, // cap referenced inbox note excerpts injected via task context resolution
|
|
735
|
+
maxResolvedTaskContextBytes: 20 * 1024, // bound the total implicit context injected from referenced plans/notes
|
|
736
|
+
maxNotesPromptBytes: 8 * 1024, // cap Team Notes injected into every playbook prompt
|
|
737
|
+
maxMeetingPromptBytes: 16 * 1024, // cap meeting findings/debate context injected into prompts
|
|
738
|
+
maxMeetingHumanNotesBytes: 2 * 1024, // cap human note bullet lists injected into meeting prompts
|
|
739
|
+
maxPipelineMeetingContextBytes: 16 * 1024, // cap aggregated meeting/dependency context for pipeline plan generation
|
|
740
|
+
notesArchiveMaxFiles: 2000, // keep notes/archive bounded during periodic cleanup
|
|
690
741
|
// Teams integration — config.teams shape: { enabled, appId, appPassword, certPath, privateKeyPath, tenantId, notifyEvents, ccMirror, inboxPollInterval }
|
|
691
742
|
// Auth modes: (1) appId + appPassword (client secret), or (2) appId + certPath + privateKeyPath + tenantId (certificate)
|
|
692
743
|
teams: {
|
|
@@ -1527,6 +1578,9 @@ module.exports = {
|
|
|
1527
1578
|
mutatePullRequests,
|
|
1528
1579
|
uid,
|
|
1529
1580
|
uniquePath,
|
|
1581
|
+
truncateTextBytes,
|
|
1582
|
+
tailTextBytes,
|
|
1583
|
+
appendTextTail,
|
|
1530
1584
|
writeToInbox, parseNoteId,
|
|
1531
1585
|
exec,
|
|
1532
1586
|
execAsync,
|
package/engine.js
CHANGED
|
@@ -2512,9 +2512,10 @@ function normalizeAc(ac) {
|
|
|
2512
2512
|
function buildWorkItemDispatchVars(item, vars, config, options = {}) {
|
|
2513
2513
|
const { worktreePath, includeNotes = true, workType } = options;
|
|
2514
2514
|
|
|
2515
|
-
//
|
|
2515
|
+
// Team notes are injected once in renderPlaybook(). Keep notes_content as a
|
|
2516
|
+
// lightweight pointer so prompts do not duplicate the full notes.md blob.
|
|
2516
2517
|
if (includeNotes) {
|
|
2517
|
-
vars.notes_content =
|
|
2518
|
+
vars.notes_content = 'See the **Team Notes** section above for the latest shared team context.';
|
|
2518
2519
|
}
|
|
2519
2520
|
|
|
2520
2521
|
// References
|
|
@@ -2561,7 +2562,7 @@ function buildWorkItemDispatchVars(item, vars, config, options = {}) {
|
|
|
2561
2562
|
if (workType === WORK_TYPE.ASK) {
|
|
2562
2563
|
vars.question = item.title + (item.description ? '\n\n' + item.description : '');
|
|
2563
2564
|
vars.task_id = item.id;
|
|
2564
|
-
vars.notes_content =
|
|
2565
|
+
vars.notes_content = 'See the **Team Notes** section above for the latest shared team context.';
|
|
2565
2566
|
}
|
|
2566
2567
|
|
|
2567
2568
|
// Resolve implicit context references (e.g., "ripley's plan", "the latest plan")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1048",
|
|
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"
|