@yemi33/minions 0.1.955 → 0.1.957
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 -3
- package/dashboard/js/settings.js +2 -0
- package/dashboard.js +1 -0
- package/engine/playbook.js +19 -2
- package/engine/shared.js +1 -0
- package/engine.js +53 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.957 (2026-04-14)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- wire agentBusyReassignMs into settings UI and persist
|
|
6
7
|
- ADO throttle detection, poll guards, and dashboard banner (#1051)
|
|
7
8
|
- gate auto-fix conflict dispatch behind autoFixConflicts flag
|
|
8
9
|
- add PR/build status CLI shim and agent guidance
|
|
@@ -22,9 +23,9 @@
|
|
|
22
23
|
- fix dashboard plan-pause nested lock violation (#968)
|
|
23
24
|
- add missing branch_name to central dispatch vars (#967)
|
|
24
25
|
- fix dispatch.js mutator fallback to use nullish coalescing (#966)
|
|
25
|
-
- fix stall recovery nested lock violation (#965)
|
|
26
26
|
|
|
27
27
|
### Fixes
|
|
28
|
+
- suppress warn for optional template variables (#1061)
|
|
28
29
|
- fix ENGINE_DEFAULTS ref and derive boolean settings from defaults
|
|
29
30
|
- add autoFixConflicts to boolean settings persist list
|
|
30
31
|
- tighten build query guard to require mergeCommitId
|
|
@@ -44,7 +45,6 @@
|
|
|
44
45
|
- qaAbort race + skip kill on completed doc-chat
|
|
45
46
|
- doc-chat Stop button actually works — release server guard on disconnect
|
|
46
47
|
- CC streaming auto-retries fresh session when resume fails
|
|
47
|
-
- 429 retry on abort race applies to all sends, not just queued
|
|
48
48
|
|
|
49
49
|
### Other
|
|
50
50
|
- test(engine): add regression tests for autoFixConflicts flag and DEFAULTS alias
|
package/dashboard/js/settings.js
CHANGED
|
@@ -61,6 +61,7 @@ async function openSettings() {
|
|
|
61
61
|
settingsField('Eval Max Iterations', 'set-evalMaxIterations', e.evalMaxIterations || 3, '', 'Max review→fix cycles before escalating (1-10)') +
|
|
62
62
|
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)') +
|
|
63
63
|
settingsField('Max Build Fix Attempts', 'set-maxBuildFixAttempts', e.maxBuildFixAttempts || 3, '', 'Max auto-fix dispatches per PR before escalating to human (1-10)') +
|
|
64
|
+
settingsField('Agent Busy Reassign', 'set-agentBusyReassignMs', e.agentBusyReassignMs || 600000, 'ms', 'Reassign work to another agent after it waits this long on a busy agent') +
|
|
64
65
|
settingsField('Version Check Interval', 'set-versionCheckInterval', e.versionCheckInterval || 3600000, 'ms', 'How often to check npm for updates (default: 1 hour)') +
|
|
65
66
|
settingsField('Ignored Comment Authors', 'set-ignoredCommentAuthors', (e.ignoredCommentAuthors || []).join(', '), '', 'Comma-separated usernames — comments auto-closed, never trigger fixes') +
|
|
66
67
|
'</div>' +
|
|
@@ -246,6 +247,7 @@ async function saveSettings() {
|
|
|
246
247
|
evalMaxIterations: document.getElementById('set-evalMaxIterations').value,
|
|
247
248
|
evalMaxCost: document.getElementById('set-evalMaxCost').value || null,
|
|
248
249
|
maxBuildFixAttempts: document.getElementById('set-maxBuildFixAttempts').value,
|
|
250
|
+
agentBusyReassignMs: document.getElementById('set-agentBusyReassignMs').value,
|
|
249
251
|
ignoredCommentAuthors: document.getElementById('set-ignoredCommentAuthors').value,
|
|
250
252
|
versionCheckInterval: document.getElementById('set-versionCheckInterval').value,
|
|
251
253
|
ccModel: document.getElementById('set-ccModel').value,
|
package/dashboard.js
CHANGED
|
@@ -3754,6 +3754,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3754
3754
|
versionCheckInterval: [60000],
|
|
3755
3755
|
maxBuildFixAttempts: [1, 10],
|
|
3756
3756
|
adoPollStatusEvery: [1], adoPollCommentsEvery: [1],
|
|
3757
|
+
agentBusyReassignMs: [60000],
|
|
3757
3758
|
};
|
|
3758
3759
|
for (const [key, [min, max]] of Object.entries(numericFields)) {
|
|
3759
3760
|
if (e[key] !== undefined) {
|
package/engine/playbook.js
CHANGED
|
@@ -214,6 +214,22 @@ function resolveTaskContext(item, config) {
|
|
|
214
214
|
// are optional by design. Only variables that make the playbook non-functional
|
|
215
215
|
// when absent are listed here.
|
|
216
216
|
|
|
217
|
+
// ─── Optional Template Variables ────────────────────────────────────────────
|
|
218
|
+
// Variables that legitimately resolve to empty string for ad-hoc work items.
|
|
219
|
+
// These are silently filtered out of the empty-string warning to avoid
|
|
220
|
+
// masking real warnings. Add new optional vars here when they are contextual
|
|
221
|
+
// (plan-linked, checkpoint-dependent, etc.) and not required for the playbook
|
|
222
|
+
// to function.
|
|
223
|
+
|
|
224
|
+
const PLAYBOOK_OPTIONAL_VARS = new Set([
|
|
225
|
+
'source_plan', // only set when work item is linked to a plan
|
|
226
|
+
'plan_slug', // derived from source_plan
|
|
227
|
+
'additional_context', // only set when item has a prompt
|
|
228
|
+
'references', // only set when item.references has entries
|
|
229
|
+
'acceptance_criteria', // only set when item.acceptanceCriteria has entries
|
|
230
|
+
'checkpoint_context', // only set when resuming from a prior timeout
|
|
231
|
+
]);
|
|
232
|
+
|
|
217
233
|
const PLAYBOOK_REQUIRED_VARS = {
|
|
218
234
|
'implement': ['item_id', 'item_name', 'branch_name', 'project_path'],
|
|
219
235
|
'implement-shared': ['item_id', 'item_name', 'branch_name', 'worktree_path'],
|
|
@@ -364,9 +380,9 @@ function renderPlaybook(type, vars) {
|
|
|
364
380
|
log('warn', `Playbook "${type}": substituted values contain unresolved {{...}} patterns (potential self-reference): ${selfRefVars.join(', ')}`);
|
|
365
381
|
}
|
|
366
382
|
|
|
367
|
-
// Warn on variables that resolved to empty string
|
|
383
|
+
// Warn on variables that resolved to empty string (skip known-optional vars)
|
|
368
384
|
const emptyVars = Object.entries(allVars)
|
|
369
|
-
.filter(([, val]) => String(val) === '')
|
|
385
|
+
.filter(([key, val]) => String(val) === '' && !PLAYBOOK_OPTIONAL_VARS.has(key))
|
|
370
386
|
.map(([key]) => key);
|
|
371
387
|
if (emptyVars.length > 0) {
|
|
372
388
|
log('warn', `Playbook "${type}": template variables resolved to empty string: ${emptyVars.join(', ')}`);
|
|
@@ -551,6 +567,7 @@ module.exports = {
|
|
|
551
567
|
renderPlaybook,
|
|
552
568
|
validatePlaybookVars,
|
|
553
569
|
PLAYBOOK_REQUIRED_VARS,
|
|
570
|
+
PLAYBOOK_OPTIONAL_VARS,
|
|
554
571
|
buildSystemPrompt,
|
|
555
572
|
buildAgentContext,
|
|
556
573
|
selectPlaybook,
|
package/engine/shared.js
CHANGED
|
@@ -555,6 +555,7 @@ const ENGINE_DEFAULTS = {
|
|
|
555
555
|
autoCompletePrs: false, // auto-merge PRs when builds green + review approved (opt-in)
|
|
556
556
|
prMergeMethod: 'squash', // merge method: squash, merge, rebase
|
|
557
557
|
ignoredCommentAuthors: [], // comments from these authors are auto-closed and never trigger fixes
|
|
558
|
+
agentBusyReassignMs: 600000, // 10min — reassign work item to another agent if preferred agent is busy beyond this threshold
|
|
558
559
|
ccModel: 'sonnet', // model for Command Center and doc-chat (sonnet, haiku, opus)
|
|
559
560
|
ccEffort: null, // effort level for CC/doc-chat (null, 'low', 'medium', 'high')
|
|
560
561
|
heartbeatTimeouts: {}, // populated after WORK_TYPE is defined (below)
|
package/engine.js
CHANGED
|
@@ -1300,6 +1300,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1300
1300
|
const item = dispatch.pending.splice(idx, 1)[0];
|
|
1301
1301
|
item.started_at = startedAt;
|
|
1302
1302
|
delete item.skipReason;
|
|
1303
|
+
delete item._agentBusySince;
|
|
1303
1304
|
if (!dispatch.active.some(d => d.id === id)) {
|
|
1304
1305
|
dispatch.active.push(item);
|
|
1305
1306
|
}
|
|
@@ -3324,7 +3325,46 @@ async function tickInner() {
|
|
|
3324
3325
|
log('warn', `Duplicate dispatch ID ${item.id} in pending queue — skipping`);
|
|
3325
3326
|
continue;
|
|
3326
3327
|
}
|
|
3327
|
-
if (busyAgents.has(item.agent))
|
|
3328
|
+
if (busyAgents.has(item.agent)) {
|
|
3329
|
+
// Agent busy reassignment: if item has been waiting on a busy agent past the threshold,
|
|
3330
|
+
// try to find an alternative agent via routing. Skip explicitly assigned items.
|
|
3331
|
+
const reassignMs = config.engine?.agentBusyReassignMs ?? ENGINE_DEFAULTS.agentBusyReassignMs;
|
|
3332
|
+
const isExplicitReassign = !!item.meta?.item?.agent;
|
|
3333
|
+
if (!isExplicitReassign && reassignMs > 0 && item._agentBusySince) {
|
|
3334
|
+
const busySinceMs = new Date(item._agentBusySince).getTime();
|
|
3335
|
+
if (Date.now() - busySinceMs > reassignMs) {
|
|
3336
|
+
const originalAgent = item.agent;
|
|
3337
|
+
const altAgent = resolveAgent(item.type, config);
|
|
3338
|
+
if (altAgent && altAgent !== originalAgent && !busyAgents.has(altAgent)) {
|
|
3339
|
+
log('info', `Reassigning ${item.id} from ${originalAgent} to ${altAgent} — agent busy > ${reassignMs}ms`);
|
|
3340
|
+
item.agent = altAgent;
|
|
3341
|
+
item.agentName = config.agents[altAgent]?.name || tempAgents.get(altAgent)?.name || altAgent;
|
|
3342
|
+
item.agentRole = config.agents[altAgent]?.role || tempAgents.get(altAgent)?.role || 'Agent';
|
|
3343
|
+
delete item._agentBusySince;
|
|
3344
|
+
delete item.skipReason;
|
|
3345
|
+
// Persist reassignment to dispatch.json
|
|
3346
|
+
mutateDispatch((dp) => {
|
|
3347
|
+
const p = (dp.pending || []).find(d => d.id === item.id);
|
|
3348
|
+
if (p) {
|
|
3349
|
+
p.agent = altAgent;
|
|
3350
|
+
p.agentName = item.agentName;
|
|
3351
|
+
p.agentRole = item.agentRole;
|
|
3352
|
+
delete p._agentBusySince;
|
|
3353
|
+
delete p.skipReason;
|
|
3354
|
+
}
|
|
3355
|
+
return dp;
|
|
3356
|
+
});
|
|
3357
|
+
// Fall through to branch mutex / concurrency checks below
|
|
3358
|
+
} else {
|
|
3359
|
+
continue; // No alternative agent available — keep waiting
|
|
3360
|
+
}
|
|
3361
|
+
} else {
|
|
3362
|
+
continue; // Below threshold — keep waiting
|
|
3363
|
+
}
|
|
3364
|
+
} else {
|
|
3365
|
+
continue; // No _agentBusySince set yet or explicitly assigned — skip
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3328
3368
|
// Branch mutex: skip items targeting a branch already locked by an active or newly-dispatched task
|
|
3329
3369
|
const itemBranch = item.meta?.branch ? sanitizeBranch(item.meta.branch) : null;
|
|
3330
3370
|
if (itemBranch && lockedBranches.has(itemBranch)) continue;
|
|
@@ -3412,6 +3452,18 @@ async function tickInner() {
|
|
|
3412
3452
|
reason = 'branch_locked';
|
|
3413
3453
|
}
|
|
3414
3454
|
}
|
|
3455
|
+
// Track when item first became blocked on a busy agent for reassignment threshold
|
|
3456
|
+
if (reason === 'agent_busy') {
|
|
3457
|
+
if (!item._agentBusySince) {
|
|
3458
|
+
item._agentBusySince = ts();
|
|
3459
|
+
skipReasonChanged = true;
|
|
3460
|
+
}
|
|
3461
|
+
} else {
|
|
3462
|
+
if (item._agentBusySince) {
|
|
3463
|
+
delete item._agentBusySince;
|
|
3464
|
+
skipReasonChanged = true;
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3415
3467
|
if (item.skipReason !== reason) {
|
|
3416
3468
|
item.skipReason = reason;
|
|
3417
3469
|
skipReasonChanged = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.957",
|
|
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"
|