@yemi33/minions 0.1.295 → 0.1.296
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/consolidation.js +26 -9
- package/engine/cooldown.js +5 -1
- package/engine/playbook.js +8 -0
- package/engine/scheduler.js +1 -1
- package/engine/spawn-agent.js +20 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/engine/consolidation.js
CHANGED
|
@@ -224,12 +224,22 @@ function consolidateWithLLM(items, existingNotes, files, config) {
|
|
|
224
224
|
let newContent = current + entry;
|
|
225
225
|
|
|
226
226
|
if (newContent.length > 50000) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
227
|
+
// Truncate on section boundary — scan backward for last \n# before byte limit
|
|
228
|
+
// Never cut mid-section to preserve readability
|
|
229
|
+
const limit = 50000;
|
|
230
|
+
const lastSectionBoundary = newContent.lastIndexOf('\n---\n\n### ', limit);
|
|
231
|
+
if (lastSectionBoundary > 0) {
|
|
232
|
+
newContent = newContent.slice(0, lastSectionBoundary);
|
|
233
|
+
log('info', `Pruned notes.md at section boundary (pos ${lastSectionBoundary}) to stay under ${limit} bytes`);
|
|
234
|
+
} else {
|
|
235
|
+
// Fallback: use the old section-count approach
|
|
236
|
+
const sections = newContent.split('\n---\n\n### ');
|
|
237
|
+
if (sections.length > 10) {
|
|
238
|
+
const header = sections[0];
|
|
239
|
+
const recent = sections.slice(-8);
|
|
240
|
+
newContent = header + '\n---\n\n### ' + recent.join('\n---\n\n### ');
|
|
241
|
+
log('info', `Pruned notes.md: removed ${sections.length - 9} old sections`);
|
|
242
|
+
}
|
|
233
243
|
}
|
|
234
244
|
}
|
|
235
245
|
|
|
@@ -312,8 +322,9 @@ function consolidateWithRegex(items, files) {
|
|
|
312
322
|
const seen = new Map();
|
|
313
323
|
const deduped = [];
|
|
314
324
|
for (const insight of allInsights) {
|
|
315
|
-
const fpWords = insight.fingerprint.split(' ').filter(w => w.length > 4).slice(0, 5);
|
|
325
|
+
const fpWords = insight.fingerprint.split(' ').filter(w => w.length > 4 && w.length <= 200).slice(0, 5);
|
|
316
326
|
// Use word-boundary regex to avoid substring false positives (e.g. 'fix' matching 'prefix')
|
|
327
|
+
// Cap word length at 200 chars to prevent ReDoS on pathological input
|
|
317
328
|
if (fpWords.length >= 3 && fpWords.every(w => new RegExp(`\\b${w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(existingNotes))) continue;
|
|
318
329
|
const existing = seen.get(insight.fingerprint);
|
|
319
330
|
if (existing) { if (!existing.sources.includes(insight.agent)) existing.sources.push(insight.agent); continue; }
|
|
@@ -355,8 +366,14 @@ function consolidateWithRegex(items, files) {
|
|
|
355
366
|
const current = getNotes();
|
|
356
367
|
let newContent = current + entry;
|
|
357
368
|
if (newContent.length > 50000) {
|
|
358
|
-
const
|
|
359
|
-
|
|
369
|
+
const limit = 50000;
|
|
370
|
+
const lastBoundary = newContent.lastIndexOf('\n---\n\n### ', limit);
|
|
371
|
+
if (lastBoundary > 0) {
|
|
372
|
+
newContent = newContent.slice(0, lastBoundary);
|
|
373
|
+
} else {
|
|
374
|
+
const sections = newContent.split('\n---\n\n### ');
|
|
375
|
+
if (sections.length > 10) { newContent = sections[0] + '\n---\n\n### ' + sections.slice(-8).join('\n---\n\n### '); }
|
|
376
|
+
}
|
|
360
377
|
}
|
|
361
378
|
safeWrite(NOTES_PATH, newContent);
|
|
362
379
|
classifyToKnowledgeBase(items);
|
package/engine/cooldown.js
CHANGED
|
@@ -38,7 +38,11 @@ function saveCooldowns() {
|
|
|
38
38
|
if (now - v.timestamp > 24 * 60 * 60 * 1000) dispatchCooldowns.delete(k);
|
|
39
39
|
}
|
|
40
40
|
const obj = Object.fromEntries(dispatchCooldowns);
|
|
41
|
-
|
|
41
|
+
try {
|
|
42
|
+
safeWrite(COOLDOWN_PATH, obj);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
log('warn', `saveCooldowns failed writing ${COOLDOWN_PATH}: ${err.message}`);
|
|
45
|
+
}
|
|
42
46
|
}, 1000); // debounce — write at most once per second
|
|
43
47
|
}
|
|
44
48
|
|
package/engine/playbook.js
CHANGED
|
@@ -285,6 +285,14 @@ function renderPlaybook(type, vars) {
|
|
|
285
285
|
content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), String(val));
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
// Warn when a substituted value itself contains {{...}} patterns (potential self-reference)
|
|
289
|
+
const selfRefVars = Object.entries(allVars)
|
|
290
|
+
.filter(([, val]) => /\{\{\w+\}\}/.test(String(val)))
|
|
291
|
+
.map(([key]) => key);
|
|
292
|
+
if (selfRefVars.length > 0) {
|
|
293
|
+
log('warn', `Playbook "${type}": substituted values contain unresolved {{...}} patterns (potential self-reference): ${selfRefVars.join(', ')}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
288
296
|
// Warn on variables that resolved to empty string
|
|
289
297
|
const emptyVars = Object.entries(allVars)
|
|
290
298
|
.filter(([, val]) => String(val) === '')
|
package/engine/scheduler.js
CHANGED
|
@@ -114,7 +114,7 @@ function discoverScheduledWork(config) {
|
|
|
114
114
|
mutateJsonFileLocked(SCHEDULE_RUNS_PATH, (runs) => {
|
|
115
115
|
for (const sched of schedules) {
|
|
116
116
|
if (!sched.id || !sched.cron || !sched.title) continue;
|
|
117
|
-
if (sched.enabled
|
|
117
|
+
if (sched.enabled !== true) continue; // explicit true required — undefined/null/false all skip
|
|
118
118
|
|
|
119
119
|
const lastRun = runs[sched.id] || null;
|
|
120
120
|
if (!shouldRunNow(sched, lastRun)) continue;
|
package/engine/spawn-agent.js
CHANGED
|
@@ -154,17 +154,31 @@ const pidFile = promptFile.replace(/prompt-/, 'pid-').replace(/\.md$/, '.pid');
|
|
|
154
154
|
fs.writeFileSync(pidFile, String(proc.pid || ''));
|
|
155
155
|
|
|
156
156
|
// Send prompt via stdin — if system prompt was truncated, prepend the full context
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
157
|
+
try {
|
|
158
|
+
if (!isResume && Buffer.byteLength(sysPrompt) >= 30000) {
|
|
159
|
+
// System prompt was too large for CLI — prepend full context to user prompt
|
|
160
|
+
proc.stdin.write(`## Full Agent Context\n\n${sysPrompt}\n\n---\n\n## Your Task\n\n${prompt}`);
|
|
161
|
+
} else {
|
|
162
|
+
proc.stdin.write(prompt);
|
|
163
|
+
}
|
|
164
|
+
proc.stdin.end();
|
|
165
|
+
} catch (err) {
|
|
166
|
+
console.error(`FATAL: stdin write failed (broken pipe): ${err.message}`);
|
|
167
|
+
fs.appendFileSync(debugPath, `STDIN ERROR: ${err.message}\n`);
|
|
168
|
+
try { proc.kill('SIGTERM'); } catch { /* process may already be dead */ }
|
|
169
|
+
process.exit(1);
|
|
162
170
|
}
|
|
163
|
-
proc.stdin.end();
|
|
164
171
|
|
|
165
172
|
// Clean up temp file (only created for non-resume sessions)
|
|
166
173
|
if (!isResume) setTimeout(() => { try { fs.unlinkSync(sysTmpPath); } catch { /* cleanup */ } }, 5000);
|
|
167
174
|
|
|
175
|
+
// Register exit handler to clean up orphaned temp files (system prompt tmp)
|
|
176
|
+
function _cleanupSpawnTempFiles() {
|
|
177
|
+
try { fs.unlinkSync(sysTmpPath); } catch { /* may already be cleaned */ }
|
|
178
|
+
}
|
|
179
|
+
process.on('exit', _cleanupSpawnTempFiles);
|
|
180
|
+
process.on('SIGTERM', () => { _cleanupSpawnTempFiles(); process.exit(143); });
|
|
181
|
+
|
|
168
182
|
// Capture stderr separately for debugging
|
|
169
183
|
let stderrBuf = '';
|
|
170
184
|
proc.stderr.on('data', (chunk) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.296",
|
|
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"
|