@yemi33/minions 0.1.798 → 0.1.800
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 +3 -1
- package/dashboard/js/settings.js +2 -0
- package/dashboard.js +35 -17
- package/engine/ado.js +8 -7
- package/engine/github.js +6 -4
- package/engine/lifecycle.js +2 -2
- package/engine/shared.js +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.800 (2026-04-10)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- configurable ignored comment authors — auto-filtered, never trigger fixes
|
|
6
7
|
- add open-source community sources to agentic research pipeline
|
|
7
8
|
- broaden daily pipeline research from Anthropic-only to full agentic space
|
|
8
9
|
- cap review→fix cycles at evalMaxIterations (default 3)
|
|
9
10
|
|
|
10
11
|
### Fixes
|
|
12
|
+
- approved is permanent — no path can ever downgrade it
|
|
11
13
|
- allow fix dispatch on approved PRs (only guard is in updatePrAfterFix)
|
|
12
14
|
- bot comment on approved PR can no longer reset vote
|
|
13
15
|
- remove redundant 'PR' prefix from dispatch labels (id already has it)
|
package/dashboard/js/settings.js
CHANGED
|
@@ -54,6 +54,7 @@ async function openSettings() {
|
|
|
54
54
|
settingsField('Eval Max Cost', 'set-evalMaxCost', e.evalMaxCost === null || e.evalMaxCost === undefined ? '' : e.evalMaxCost, '$', 'USD ceiling per work item across all eval iterations (blank = no limit)') +
|
|
55
55
|
settingsField('Max Build Fix Attempts', 'set-maxBuildFixAttempts', e.maxBuildFixAttempts || 3, '', 'Max auto-fix dispatches per PR before escalating to human (1-10)') +
|
|
56
56
|
settingsField('Version Check Interval', 'set-versionCheckInterval', e.versionCheckInterval || 3600000, 'ms', 'How often to check npm for updates (default: 1 hour)') +
|
|
57
|
+
settingsField('Ignored Comment Authors', 'set-ignoredCommentAuthors', (e.ignoredCommentAuthors || []).join(', '), '', 'Comma-separated usernames — comments auto-closed, never trigger fixes') +
|
|
57
58
|
'</div>' +
|
|
58
59
|
|
|
59
60
|
'<h3 style="font-size:13px;color:var(--blue);margin-bottom:8px">Max Turns by Task Type</h3>' +
|
|
@@ -210,6 +211,7 @@ async function saveSettings() {
|
|
|
210
211
|
evalMaxIterations: document.getElementById('set-evalMaxIterations').value,
|
|
211
212
|
evalMaxCost: document.getElementById('set-evalMaxCost').value || null,
|
|
212
213
|
maxBuildFixAttempts: document.getElementById('set-maxBuildFixAttempts').value,
|
|
214
|
+
ignoredCommentAuthors: document.getElementById('set-ignoredCommentAuthors').value,
|
|
213
215
|
versionCheckInterval: document.getElementById('set-versionCheckInterval').value,
|
|
214
216
|
ccModel: document.getElementById('set-ccModel').value,
|
|
215
217
|
ccEffort: document.getElementById('set-ccEffort').value || null,
|
package/dashboard.js
CHANGED
|
@@ -461,7 +461,8 @@ setInterval(() => {
|
|
|
461
461
|
|
|
462
462
|
// ── Command Center: session state + helpers ─────────────────────────────────
|
|
463
463
|
|
|
464
|
-
|
|
464
|
+
// Sessions never expire by time — user controls lifecycle via tabs (new tab = new session, /clear = reset).
|
|
465
|
+
// Only invalidated by: system prompt change, explicit clear, or tab close.
|
|
465
466
|
const CC_SESSION_MAX_TURNS = Infinity;
|
|
466
467
|
let ccSession = { sessionId: null, createdAt: null, lastActiveAt: null, turnCount: 0 };
|
|
467
468
|
const ccInFlightTabs = new Set(); // per-tab in-flight tracking for parallel CC requests
|
|
@@ -477,17 +478,13 @@ function ccSessionValid() {
|
|
|
477
478
|
ccSession = { sessionId: null, createdAt: null, lastActiveAt: null, turnCount: 0 };
|
|
478
479
|
return false;
|
|
479
480
|
}
|
|
480
|
-
|
|
481
|
-
return age < CC_SESSION_EXPIRY_MS && ccSession.turnCount < CC_SESSION_MAX_TURNS;
|
|
481
|
+
return ccSession.turnCount < CC_SESSION_MAX_TURNS;
|
|
482
482
|
}
|
|
483
483
|
|
|
484
484
|
// Load persisted CC session on startup
|
|
485
485
|
try {
|
|
486
486
|
const saved = safeJson(path.join(ENGINE_DIR, 'cc-session.json'));
|
|
487
|
-
if (saved && saved.sessionId)
|
|
488
|
-
const age = Date.now() - new Date(saved.lastActiveAt || 0).getTime();
|
|
489
|
-
if (age < CC_SESSION_EXPIRY_MS) ccSession = saved;
|
|
490
|
-
}
|
|
487
|
+
if (saved && saved.sessionId) ccSession = saved;
|
|
491
488
|
} catch { /* optional */ }
|
|
492
489
|
|
|
493
490
|
// Static system prompt — baked into session on creation, never changes
|
|
@@ -584,10 +581,8 @@ const docSessions = new Map(); // key → { sessionId, lastActiveAt, turnCount }
|
|
|
584
581
|
try {
|
|
585
582
|
const saved = safeJson(DOC_SESSIONS_PATH);
|
|
586
583
|
if (saved && typeof saved === 'object') {
|
|
587
|
-
const now = Date.now();
|
|
588
584
|
for (const [key, s] of Object.entries(saved)) {
|
|
589
|
-
|
|
590
|
-
if (age < CC_SESSION_EXPIRY_MS && s.turnCount < CC_SESSION_MAX_TURNS) {
|
|
585
|
+
if (s.turnCount < CC_SESSION_MAX_TURNS) {
|
|
591
586
|
docSessions.set(key, s);
|
|
592
587
|
}
|
|
593
588
|
}
|
|
@@ -608,8 +603,7 @@ function resolveSession(store, key) {
|
|
|
608
603
|
if (!key) return null;
|
|
609
604
|
const s = docSessions.get(key);
|
|
610
605
|
if (!s) return null;
|
|
611
|
-
|
|
612
|
-
if (age > CC_SESSION_EXPIRY_MS || s.turnCount >= CC_SESSION_MAX_TURNS) {
|
|
606
|
+
if (s.turnCount >= CC_SESSION_MAX_TURNS) {
|
|
613
607
|
docSessions.delete(key);
|
|
614
608
|
persistDocSessions();
|
|
615
609
|
return null;
|
|
@@ -3342,9 +3336,15 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3342
3336
|
ccInFlightTabs.add(tabId);
|
|
3343
3337
|
|
|
3344
3338
|
try {
|
|
3339
|
+
let sessionReset = false;
|
|
3345
3340
|
if (body.sessionId && body.sessionId !== ccSession.sessionId) {
|
|
3346
3341
|
ccSession = { sessionId: null, createdAt: null, lastActiveAt: null, turnCount: 0 };
|
|
3347
3342
|
}
|
|
3343
|
+
// Detect prompt hash change — force fresh session
|
|
3344
|
+
if (body.sessionId && ccSession._promptHash && ccSession._promptHash !== _ccPromptHash) {
|
|
3345
|
+
ccSession = { sessionId: null, createdAt: null, lastActiveAt: null, turnCount: 0 };
|
|
3346
|
+
sessionReset = true;
|
|
3347
|
+
}
|
|
3348
3348
|
const wasResume = !!(body.sessionId && body.sessionId === ccSession.sessionId && ccSessionValid());
|
|
3349
3349
|
|
|
3350
3350
|
const result = await ccCall(body.message, { store: 'cc' });
|
|
@@ -3365,7 +3365,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3365
3365
|
});
|
|
3366
3366
|
}
|
|
3367
3367
|
|
|
3368
|
-
|
|
3368
|
+
const reply = { ...parseCCActions(result.text), sessionId: ccSession.sessionId, newSession: !wasResume };
|
|
3369
|
+
if (sessionReset) reply.sessionReset = true;
|
|
3370
|
+
return jsonReply(res, 200, reply);
|
|
3369
3371
|
} finally {
|
|
3370
3372
|
ccInFlightTabs.delete(tabId);
|
|
3371
3373
|
}
|
|
@@ -3397,7 +3399,17 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3397
3399
|
|
|
3398
3400
|
try {
|
|
3399
3401
|
// Session management — per-tab: use sessionId from request, don't mutate global ccSession
|
|
3400
|
-
|
|
3402
|
+
let tabSessionId = body.sessionId || null;
|
|
3403
|
+
let sessionReset = false;
|
|
3404
|
+
// If system prompt changed since this session was created, force a fresh session
|
|
3405
|
+
if (tabSessionId) {
|
|
3406
|
+
const sessions = shared.safeJsonArr(CC_SESSIONS_PATH);
|
|
3407
|
+
const tabEntry = sessions.find(s => s.id === (body.tabId || 'default'));
|
|
3408
|
+
if (tabEntry && tabEntry._promptHash && tabEntry._promptHash !== _ccPromptHash) {
|
|
3409
|
+
tabSessionId = null;
|
|
3410
|
+
sessionReset = true;
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3401
3413
|
const wasResume = !!tabSessionId;
|
|
3402
3414
|
const sessionId = tabSessionId || null;
|
|
3403
3415
|
const preamble = wasResume ? '' : buildCCStatePreamble();
|
|
@@ -3451,10 +3463,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3451
3463
|
if (existing) {
|
|
3452
3464
|
existing.sessionId = responseSessionId;
|
|
3453
3465
|
existing.lastActiveAt = new Date(now).toISOString();
|
|
3454
|
-
existing.turnCount = (existing.turnCount || 0) + 1;
|
|
3466
|
+
existing.turnCount = sessionReset ? 1 : (existing.turnCount || 0) + 1;
|
|
3455
3467
|
existing.preview = preview;
|
|
3468
|
+
existing._promptHash = _ccPromptHash;
|
|
3456
3469
|
} else {
|
|
3457
|
-
sessions.push({ id: tabId, title: (body.message || 'New chat').slice(0, 40), sessionId: responseSessionId, createdAt: new Date(now).toISOString(), lastActiveAt: new Date(now).toISOString(), turnCount: 1, preview });
|
|
3470
|
+
sessions.push({ id: tabId, title: (body.message || 'New chat').slice(0, 40), sessionId: responseSessionId, createdAt: new Date(now).toISOString(), lastActiveAt: new Date(now).toISOString(), turnCount: 1, preview, _promptHash: _ccPromptHash });
|
|
3458
3471
|
}
|
|
3459
3472
|
safeWrite(CC_SESSIONS_PATH, sessions);
|
|
3460
3473
|
} catch { /* non-critical */ }
|
|
@@ -3462,7 +3475,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3462
3475
|
|
|
3463
3476
|
// Send final result with actions
|
|
3464
3477
|
const { text: displayText, actions } = parseCCActions(result.text);
|
|
3465
|
-
|
|
3478
|
+
const donePayload = { type: 'done', text: displayText, actions, sessionId: responseSessionId, newSession: !wasResume };
|
|
3479
|
+
if (sessionReset) donePayload.sessionReset = true;
|
|
3480
|
+
res.write('data: ' + JSON.stringify(donePayload) + '\n\n');
|
|
3466
3481
|
_ccStreamEnded = true; res.end();
|
|
3467
3482
|
} finally {
|
|
3468
3483
|
ccInFlightTabs.delete(tabId);
|
|
@@ -3651,6 +3666,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3651
3666
|
// Eval loop settings
|
|
3652
3667
|
if (e.evalMaxIterations !== undefined) config.engine.evalMaxIterations = Math.max(1, Math.min(10, Number(e.evalMaxIterations) || D.evalMaxIterations));
|
|
3653
3668
|
if (e.evalMaxCost !== undefined) config.engine.evalMaxCost = e.evalMaxCost === null || e.evalMaxCost === '' ? null : Math.max(0, Number(e.evalMaxCost) || 0);
|
|
3669
|
+
if (e.ignoredCommentAuthors !== undefined) {
|
|
3670
|
+
config.engine.ignoredCommentAuthors = String(e.ignoredCommentAuthors || '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
|
|
3671
|
+
}
|
|
3654
3672
|
}
|
|
3655
3673
|
|
|
3656
3674
|
if (body.claude) {
|
package/engine/ado.js
CHANGED
|
@@ -295,19 +295,18 @@ async function pollPrStatus(config) {
|
|
|
295
295
|
const reviewers = prData.reviewers || [];
|
|
296
296
|
const votes = reviewers.map(r => r.vote).filter(v => v !== undefined);
|
|
297
297
|
let newReviewStatus = pr.reviewStatus || 'pending';
|
|
298
|
-
|
|
298
|
+
// Once approved, it stays approved permanently
|
|
299
|
+
if (pr.reviewStatus === 'approved') {
|
|
300
|
+
newReviewStatus = 'approved';
|
|
301
|
+
} else if (votes.length > 0) {
|
|
299
302
|
if (votes.some(v => v === -10)) {
|
|
300
|
-
// Don't re-trigger 'changes-requested' if fix agent already responded (waiting state).
|
|
301
|
-
// Only re-trigger if there's been a new push since the fix (reviewer re-reviewed).
|
|
302
303
|
if (pr.reviewStatus === 'waiting' && pr.minionsReview?.fixedAt && (!pr.lastPushedAt || pr.lastPushedAt <= pr.minionsReview.fixedAt)) {
|
|
303
|
-
newReviewStatus = 'waiting';
|
|
304
|
+
newReviewStatus = 'waiting';
|
|
304
305
|
} else {
|
|
305
306
|
newReviewStatus = 'changes-requested';
|
|
306
307
|
}
|
|
307
308
|
}
|
|
308
309
|
else if (votes.some(v => v >= 5)) newReviewStatus = 'approved';
|
|
309
|
-
// Never downgrade from 'approved' — only -10 (reject) can override it
|
|
310
|
-
else if (pr.reviewStatus === 'approved') newReviewStatus = 'approved';
|
|
311
310
|
else if (votes.some(v => v === -5)) newReviewStatus = 'waiting';
|
|
312
311
|
else newReviewStatus = 'pending';
|
|
313
312
|
}
|
|
@@ -502,8 +501,10 @@ async function pollPrHumanComments(config) {
|
|
|
502
501
|
for (const comment of (thread.comments || [])) {
|
|
503
502
|
if (!comment.content || comment.commentType === 'system') continue;
|
|
504
503
|
const content = comment.content;
|
|
505
|
-
// Skip bots and
|
|
504
|
+
// Skip bots, CI noise, and ignored authors
|
|
506
505
|
const authorName = (comment.author?.displayName || '').toLowerCase();
|
|
506
|
+
const ignoredAuthors = (config.engine?.ignoredCommentAuthors || []).map(a => a.toLowerCase());
|
|
507
|
+
if (ignoredAuthors.some(a => authorName.includes(a))) continue;
|
|
507
508
|
if (/\b(bot|service|build|pipeline|codecov|sonar)\b/i.test(authorName)) continue;
|
|
508
509
|
if (/^#{1,3}\s*(Coverage|Build|Test|Deploy|Pipeline)\s*(Report|Status|Result|Summary)/i.test(content)) continue;
|
|
509
510
|
// Detect agent comments (included in context, but don't trigger fix)
|
package/engine/github.js
CHANGED
|
@@ -330,8 +330,10 @@ async function pollPrStatus(config) {
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
let newReviewStatus = pr.reviewStatus || 'pending';
|
|
333
|
-
|
|
334
|
-
|
|
333
|
+
// Once approved, it stays approved permanently
|
|
334
|
+
if (pr.reviewStatus === 'approved') {
|
|
335
|
+
newReviewStatus = 'approved';
|
|
336
|
+
} else if (states.some(s => s === 'CHANGES_REQUESTED')) {
|
|
335
337
|
if (pr.reviewStatus === 'waiting' && pr.minionsReview?.fixedAt && (!pr.lastPushedAt || pr.lastPushedAt <= pr.minionsReview.fixedAt)) {
|
|
336
338
|
newReviewStatus = 'waiting';
|
|
337
339
|
} else {
|
|
@@ -339,8 +341,6 @@ async function pollPrStatus(config) {
|
|
|
339
341
|
}
|
|
340
342
|
}
|
|
341
343
|
else if (states.some(s => s === 'APPROVED')) newReviewStatus = 'approved';
|
|
342
|
-
// Never downgrade from 'approved' — only CHANGES_REQUESTED can override it
|
|
343
|
-
else if (pr.reviewStatus === 'approved') newReviewStatus = 'approved';
|
|
344
344
|
else if (states.length > 0) newReviewStatus = 'pending';
|
|
345
345
|
else if (states.length === 0 && reviews.length > 0 && newReviewStatus === 'pending') newReviewStatus = 'waiting';
|
|
346
346
|
|
|
@@ -477,9 +477,11 @@ async function pollPrHumanComments(config) {
|
|
|
477
477
|
|
|
478
478
|
// Separate: agent comments (included in context, don't trigger fix) vs human comments (trigger fix)
|
|
479
479
|
// All non-bot, non-CI comments go into context. Only non-agent comments trigger pendingFix.
|
|
480
|
+
const ignoredAuthors = new Set((config.engine?.ignoredCommentAuthors || []).map(a => a.toLowerCase()));
|
|
480
481
|
function _isBot(c) {
|
|
481
482
|
if (c.user?.type === 'Bot') return true;
|
|
482
483
|
const login = (c.user?.login || '').toLowerCase();
|
|
484
|
+
if (ignoredAuthors.has(login)) return true;
|
|
483
485
|
if (/\b(bot|codecov|sonar|dependabot|renovate|github-actions|azure-pipelines)\b/i.test(login)) return true;
|
|
484
486
|
const body = c.body || '';
|
|
485
487
|
if (/^#{1,3}\s*(Coverage|Build|Test|Deploy|Pipeline)\s*(Report|Status|Result|Summary)/i.test(body)) return true;
|
package/engine/lifecycle.js
CHANGED
|
@@ -751,8 +751,8 @@ async function updatePrAfterReview(agentId, pr, project, config, resultSummary)
|
|
|
751
751
|
if (!Array.isArray(prs)) return prs;
|
|
752
752
|
const target = prs.find(p => p.id === pr.id);
|
|
753
753
|
if (!target) return prs;
|
|
754
|
-
//
|
|
755
|
-
if (postReviewStatus &&
|
|
754
|
+
// Once approved, stays approved permanently — no path can downgrade
|
|
755
|
+
if (postReviewStatus && target.reviewStatus !== 'approved') {
|
|
756
756
|
target.reviewStatus = postReviewStatus;
|
|
757
757
|
}
|
|
758
758
|
target.lastReviewedAt = ts();
|
package/engine/shared.js
CHANGED
|
@@ -549,6 +549,7 @@ const ENGINE_DEFAULTS = {
|
|
|
549
549
|
buildFixGracePeriod: 600000, // 10min — wait for CI to run after build fix before re-dispatching
|
|
550
550
|
autoCompletePrs: false, // auto-merge PRs when builds green + review approved (opt-in)
|
|
551
551
|
prMergeMethod: 'squash', // merge method: squash, merge, rebase
|
|
552
|
+
ignoredCommentAuthors: [], // comments from these authors are auto-closed and never trigger fixes
|
|
552
553
|
ccModel: 'sonnet', // model for Command Center and doc-chat (sonnet, haiku, opus)
|
|
553
554
|
ccEffort: null, // effort level for CC/doc-chat (null, 'low', 'medium', 'high')
|
|
554
555
|
ccMaxTurns: 50, // max tool-use turns for CC/doc-chat before CLI stops
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.800",
|
|
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"
|