@yemi33/minions 0.1.1667 → 0.1.1669
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 +10 -0
- package/engine/copilot-models.json +1 -1
- package/engine/lifecycle.js +1 -2
- package/engine.js +61 -53
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/engine/lifecycle.js
CHANGED
|
@@ -1416,9 +1416,8 @@ function updatePrAfterFix(pr, project, source) {
|
|
|
1416
1416
|
if (!target) return prs;
|
|
1417
1417
|
// Never downgrade from approved — fix was dispatched but PR is already approved
|
|
1418
1418
|
if (target.reviewStatus !== 'approved') target.reviewStatus = 'waiting';
|
|
1419
|
-
// Always clear pendingFix — a fix dispatch (regardless of source) addresses all pending feedback
|
|
1420
|
-
if (target.humanFeedback) target.humanFeedback.pendingFix = false;
|
|
1421
1419
|
if (source === 'pr-human-feedback') {
|
|
1420
|
+
if (target.humanFeedback) target.humanFeedback.pendingFix = false;
|
|
1422
1421
|
target.minionsReview = { ...target.minionsReview, note: 'Fixed human feedback, awaiting re-review', fixedAt: ts() };
|
|
1423
1422
|
log('info', `Updated ${pr.id} → cleared humanFeedback.pendingFix, reset to waiting for re-review`);
|
|
1424
1423
|
} else {
|
package/engine.js
CHANGED
|
@@ -32,7 +32,7 @@ const queries = require('./engine/queries');
|
|
|
32
32
|
|
|
33
33
|
// ─── Paths ──────────────────────────────────────────────────────────────────
|
|
34
34
|
|
|
35
|
-
const MINIONS_DIR =
|
|
35
|
+
const MINIONS_DIR = shared.MINIONS_DIR;
|
|
36
36
|
const ROUTING_PATH = path.join(MINIONS_DIR, 'routing.md');
|
|
37
37
|
const PLAYBOOKS_DIR = path.join(MINIONS_DIR, 'playbooks');
|
|
38
38
|
const ARCHIVE_DIR = path.join(MINIONS_DIR, 'notes', 'archive');
|
|
@@ -2745,9 +2745,9 @@ function discoverFromWorkItems(config, project) {
|
|
|
2745
2745
|
}
|
|
2746
2746
|
const agentHints = routing.extractAgentHints(item);
|
|
2747
2747
|
const hasAgentHints = Array.isArray(agentHints) && agentHints.length > 0;
|
|
2748
|
-
const
|
|
2749
|
-
|
|
2750
|
-
|
|
2748
|
+
const hardPinnedAgent = routing.getHardPinnedAgent(item, config.agents || {});
|
|
2749
|
+
const hardPinRequested = !!hardPinnedAgent;
|
|
2750
|
+
let agentId = hardPinnedAgent || resolveAgent(workType, config, { agentHints });
|
|
2751
2751
|
let reservedAgentId = agentId;
|
|
2752
2752
|
const cfgAgents = config.agents || {};
|
|
2753
2753
|
const budgetBlocked = Object.keys(cfgAgents).some(id => {
|
|
@@ -2865,6 +2865,17 @@ function discoverFromWorkItems(config, project) {
|
|
|
2865
2865
|
return newWork;
|
|
2866
2866
|
}
|
|
2867
2867
|
|
|
2868
|
+
|
|
2869
|
+
function getSyntheticCentralProject() {
|
|
2870
|
+
const base = path.basename(MINIONS_DIR);
|
|
2871
|
+
const root = base === '.minions' ? path.dirname(MINIONS_DIR) : MINIONS_DIR;
|
|
2872
|
+
return { name: 'root', localPath: root, repoHost: 'github', repoName: path.basename(root) || 'root', mainBranch: 'main', _synthetic: true };
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
function getCentralDispatchProjects(projects) {
|
|
2876
|
+
return projects.length > 0 ? projects : [getSyntheticCentralProject()];
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2868
2879
|
/**
|
|
2869
2880
|
* Build the multi-project context section for central work items.
|
|
2870
2881
|
* Inserted into the playbook via {{scope_section}}.
|
|
@@ -3131,6 +3142,7 @@ function discoverCentralWorkItems(config) {
|
|
|
3131
3142
|
const centralPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
3132
3143
|
const items = safeJson(centralPath) || [];
|
|
3133
3144
|
const projects = getProjects(config);
|
|
3145
|
+
const dispatchProjects = getCentralDispatchProjects(projects);
|
|
3134
3146
|
const newWork = [];
|
|
3135
3147
|
// Collect mutations to apply atomically inside lock callback (avoids TOCTOU)
|
|
3136
3148
|
const mutations = new Map(); // item.id → { field: value, ... }
|
|
@@ -3190,15 +3202,14 @@ function discoverCentralWorkItems(config) {
|
|
|
3190
3202
|
|
|
3191
3203
|
const assignments = idleAgents.map((agent, i) => ({
|
|
3192
3204
|
agent,
|
|
3193
|
-
assignedProject:
|
|
3205
|
+
assignedProject: dispatchProjects[i % dispatchProjects.length]
|
|
3194
3206
|
}));
|
|
3195
3207
|
|
|
3196
3208
|
for (const { agent, assignedProject } of assignments) {
|
|
3197
3209
|
const fanKey = `${key}-${agent.id}`;
|
|
3198
3210
|
if (isAlreadyDispatched(fanKey)) continue;
|
|
3199
3211
|
|
|
3200
|
-
const ap = assignedProject ||
|
|
3201
|
-
if (!ap) { log('warn', `Fan-out: skipping ${fanKey} — no projects configured`); continue; }
|
|
3212
|
+
const ap = assignedProject || dispatchProjects[0];
|
|
3202
3213
|
const fanBranch = `fan/${item.id}/${agent.id}`;
|
|
3203
3214
|
const vars = {
|
|
3204
3215
|
...buildBaseVars(agent.id, config, ap),
|
|
@@ -3208,7 +3219,7 @@ function discoverCentralWorkItems(config) {
|
|
|
3208
3219
|
item_description: item.description || '',
|
|
3209
3220
|
work_type: workType,
|
|
3210
3221
|
additional_context: item.prompt ? `## Additional Context\n\n${item.prompt}` : '',
|
|
3211
|
-
scope_section: buildProjectContext(
|
|
3222
|
+
scope_section: buildProjectContext(dispatchProjects, assignedProject, true, agent.name, agent.role),
|
|
3212
3223
|
project_path: ap?.localPath || '',
|
|
3213
3224
|
branch_name: fanBranch,
|
|
3214
3225
|
};
|
|
@@ -3252,16 +3263,16 @@ function discoverCentralWorkItems(config) {
|
|
|
3252
3263
|
} else {
|
|
3253
3264
|
// ─── Normal: single agent dispatch ──────────────────────────────
|
|
3254
3265
|
const agentHints = routing.extractAgentHints(item);
|
|
3255
|
-
const
|
|
3256
|
-
const
|
|
3257
|
-
|
|
3266
|
+
const hardPinnedAgent = routing.getHardPinnedAgent(item, config.agents || {});
|
|
3267
|
+
const hardPinRequested = !!hardPinnedAgent;
|
|
3268
|
+
const agentId = hardPinnedAgent
|
|
3269
|
+
|| resolveAgent(workType, config, { agentHints })
|
|
3258
3270
|
|| (!hardPinRequested && workType !== WORK_TYPE.FIX ? resolveAgentReservation(workType, config, { agentHints }) : null);
|
|
3259
3271
|
if (!agentId) continue;
|
|
3260
3272
|
|
|
3261
3273
|
const agentName = config.agents[agentId]?.name || agentId;
|
|
3262
3274
|
const agentRole = config.agents[agentId]?.role || 'Agent';
|
|
3263
|
-
const firstProject =
|
|
3264
|
-
if (!firstProject) { log('warn', `Dispatch: skipping ${item.id} — no projects configured`); continue; }
|
|
3275
|
+
const firstProject = dispatchProjects[0];
|
|
3265
3276
|
|
|
3266
3277
|
// Branch mutex: skip if target branch is locked by an active dispatch
|
|
3267
3278
|
const centralBranch = item.branch || item.featureBranch || `work/${item.id}`;
|
|
@@ -3282,7 +3293,7 @@ function discoverCentralWorkItems(config) {
|
|
|
3282
3293
|
task_id: item.id,
|
|
3283
3294
|
work_type: workType,
|
|
3284
3295
|
additional_context: item.prompt ? `## Additional Context\n\n${item.prompt}` : '',
|
|
3285
|
-
scope_section: buildProjectContext(
|
|
3296
|
+
scope_section: buildProjectContext(dispatchProjects, null, false, agentName, agentRole),
|
|
3286
3297
|
project_path: firstProject?.localPath || '',
|
|
3287
3298
|
branch_name: centralBranch,
|
|
3288
3299
|
};
|
|
@@ -3384,7 +3395,7 @@ function discoverCentralWorkItems(config) {
|
|
|
3384
3395
|
agentRole,
|
|
3385
3396
|
task: item.title || item.description?.slice(0, 80) || item.id,
|
|
3386
3397
|
prompt,
|
|
3387
|
-
meta: { dispatchKey: key, source: 'central-work-item', item: { ...item, ...mutations.get(item.id) }, planFileName: item.planFile || mutations.get(item.id)?._planFileName || null, branch: item.branch || item.featureBranch || `work/${item.id}
|
|
3398
|
+
meta: { dispatchKey: key, source: 'central-work-item', item: { ...item, ...mutations.get(item.id) }, planFileName: item.planFile || mutations.get(item.id)?._planFileName || null, branch: item.branch || item.featureBranch || `work/${item.id}`, project: { name: firstProject.name, localPath: firstProject.localPath } }
|
|
3388
3399
|
});
|
|
3389
3400
|
|
|
3390
3401
|
setCooldown(key);
|
|
@@ -4026,7 +4037,19 @@ async function tickInner() {
|
|
|
4026
4037
|
persistPendingDispatchAgent(item);
|
|
4027
4038
|
} catch (e) { log('warn', `Persist agent resolution for ${item.id} failed: ${e.message}`); }
|
|
4028
4039
|
}
|
|
4029
|
-
//
|
|
4040
|
+
// Unknown configured agent: string ID that is neither configured nor a known temp agent
|
|
4041
|
+
const isUnknownAssignedAgent = typeof item.agent === 'string' && !item.agent.startsWith('temp-') && !config.agents?.[item.agent] && !tempAgents.has(item.agent);
|
|
4042
|
+
if (isUnknownAssignedAgent) {
|
|
4043
|
+
const fallback = resolvePendingDispatchAgent(item, config);
|
|
4044
|
+
if (!fallback) {
|
|
4045
|
+
log('warn', `Pending dispatch ${item.id} has unknown agent ${item.agent} and no fallback available — skipping`);
|
|
4046
|
+
continue;
|
|
4047
|
+
}
|
|
4048
|
+
log('info', `Pending dispatch ${item.id} unknown agent ${item.agent}; routed → ${fallback}`);
|
|
4049
|
+
assignPendingDispatchAgent(item, fallback, config);
|
|
4050
|
+
persistPendingDispatchAgent(item);
|
|
4051
|
+
}
|
|
4052
|
+
// #1204: Pre-assigned unspawned temp agents never unblock naturally.
|
|
4030
4053
|
// When a batch discovery saturates maxConcurrent, resolveAgent hands out temp
|
|
4031
4054
|
// IDs that get stamped onto pending items. Because those temp IDs are never
|
|
4032
4055
|
// in busyAgents (they were never spawned), the agent-busy reassignment path
|
|
@@ -4044,43 +4067,28 @@ async function tickInner() {
|
|
|
4044
4067
|
}
|
|
4045
4068
|
}
|
|
4046
4069
|
if (busyAgents.has(item.agent)) {
|
|
4047
|
-
// Agent busy reassignment:
|
|
4048
|
-
//
|
|
4049
|
-
const
|
|
4050
|
-
const
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
if (Date.now() - busySinceMs > reassignMs) {
|
|
4068
|
-
const originalAgent = item.agent;
|
|
4069
|
-
const altAgent = resolvePendingDispatchAgent(item, config);
|
|
4070
|
-
if (altAgent && altAgent !== originalAgent && !busyAgents.has(altAgent)) {
|
|
4071
|
-
log('info', `Reassigning ${item.id} from ${originalAgent} to ${altAgent} — agent busy > ${reassignMs}ms`);
|
|
4072
|
-
assignPendingDispatchAgent(item, altAgent, config);
|
|
4073
|
-
// Persist reassignment to dispatch.json
|
|
4074
|
-
persistPendingDispatchAgent(item);
|
|
4075
|
-
// Fall through to branch mutex / concurrency checks below
|
|
4076
|
-
} else {
|
|
4077
|
-
continue; // No alternative agent available — keep waiting
|
|
4078
|
-
}
|
|
4079
|
-
} else {
|
|
4080
|
-
continue; // Below threshold — keep waiting
|
|
4081
|
-
}
|
|
4070
|
+
// Agent busy reassignment: compute hard-pin status; if hard-pinned keep waiting.
|
|
4071
|
+
// For all non-hard-pinned items, reroute immediately (no threshold wait).
|
|
4072
|
+
const originalAgent = item.agent;
|
|
4073
|
+
const hardPinnedAgent = routing.getHardPinnedAgent(item.meta?.item, config.agents || {});
|
|
4074
|
+
const isHardPinned = !!hardPinnedAgent && hardPinnedAgent === originalAgent;
|
|
4075
|
+
if (isHardPinned) {
|
|
4076
|
+
continue; // Valid hard pin — keep waiting for pinned agent
|
|
4077
|
+
}
|
|
4078
|
+
// agent busy and idle alternative available — reroute immediately (no threshold)
|
|
4079
|
+
const altAgent = resolvePendingDispatchAgent(item, config);
|
|
4080
|
+
if (altAgent && altAgent !== originalAgent && !busyAgents.has(altAgent)) {
|
|
4081
|
+
log('info', `Reassigning ${item.id} from ${originalAgent} to ${altAgent} — agent busy and idle alternative available`);
|
|
4082
|
+
assignPendingDispatchAgent(item, altAgent, config);
|
|
4083
|
+
persistPendingDispatchAgent(item);
|
|
4084
|
+
// Fall through to branch mutex / concurrency checks below
|
|
4085
|
+
} else if (isSoftFixDispatch(item)) {
|
|
4086
|
+
log('info', `Clearing busy soft fix agent on ${item.id} (${originalAgent}) — waiting for any available agent`);
|
|
4087
|
+
clearPendingDispatchAgent(item);
|
|
4088
|
+
persistPendingDispatchAgent(item);
|
|
4089
|
+
continue;
|
|
4082
4090
|
} else {
|
|
4083
|
-
continue; // No
|
|
4091
|
+
continue; // No alternative agent available — keep waiting
|
|
4084
4092
|
}
|
|
4085
4093
|
}
|
|
4086
4094
|
// Branch mutex: skip items targeting a branch already locked by an active or newly-dispatched task
|
|
@@ -4217,7 +4225,7 @@ module.exports = {
|
|
|
4217
4225
|
spawnAgent, resolveAgent,
|
|
4218
4226
|
|
|
4219
4227
|
// Discovery
|
|
4220
|
-
discoverWork, discoverFromPrs, discoverFromWorkItems,
|
|
4228
|
+
discoverWork, discoverFromPrs, discoverFromWorkItems, discoverCentralWorkItems,
|
|
4221
4229
|
materializePlansAsWorkItems,
|
|
4222
4230
|
|
|
4223
4231
|
// Shared helpers (used by lifecycle.js and tests)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1669",
|
|
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"
|