@yemi33/minions 0.1.908 → 0.1.910
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 +9 -1
- package/dashboard.js +80 -56
- package/engine/dispatch.js +1 -1
- package/engine/lifecycle.js +21 -10
- package/engine.js +36 -28
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.910 (2026-04-13)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- replace magic string 'active' with PR_STATUS.ACTIVE in lifecycle.js (#969)
|
|
7
|
+
- fix dashboard plan-pause nested lock violation (#968)
|
|
8
|
+
- add missing branch_name to central dispatch vars (#967)
|
|
9
|
+
- fix dispatch.js mutator fallback to use nullish coalescing (#966)
|
|
10
|
+
- fix stall recovery nested lock violation (#965)
|
|
11
|
+
- fix pending-rebases.json race condition with file locking (#964)
|
|
12
|
+
- add missing resolveWorkItemPath import in engine.js (#963)
|
|
6
13
|
- bump plan-to-prd max turns from 20 to 35
|
|
7
14
|
- add weekly dead code & deprecated cleanup pipeline
|
|
8
15
|
- add red dot notification on CC tab when response completes (#934) (#946)
|
|
9
16
|
|
|
10
17
|
### Fixes
|
|
18
|
+
- pass sysPromptPath instead of steerPromptPath as system prompt in steering resume (#962)
|
|
11
19
|
- PRD item display dispatched/in-progress status conflation (closes #950) (#955)
|
|
12
20
|
- remove KB watchdog — checkpoint never written, restore never worked
|
|
13
21
|
- make KB sweep endpoint async with status polling (#933)
|
package/dashboard.js
CHANGED
|
@@ -2366,77 +2366,101 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2366
2366
|
safeWrite(planPath, plan);
|
|
2367
2367
|
|
|
2368
2368
|
// Propagate pause to materialized work items across all projects:
|
|
2369
|
-
// kill any active agent process and reset non-completed items
|
|
2370
|
-
|
|
2369
|
+
// kill any active agent process and reset non-completed items to paused.
|
|
2370
|
+
// Pattern: collect-then-release-then-act to avoid nested locks and long lock holds.
|
|
2371
2371
|
const wiPaths = [path.join(MINIONS_DIR, 'work-items.json')];
|
|
2372
2372
|
for (const proj of PROJECTS) {
|
|
2373
2373
|
wiPaths.push(shared.projectWorkItemsPath(proj));
|
|
2374
2374
|
}
|
|
2375
2375
|
const dispatchPath = path.join(MINIONS_DIR, 'engine', 'dispatch.json');
|
|
2376
|
-
const killedAgents = new Set();
|
|
2377
|
-
const resetItemIds = new Set();
|
|
2378
2376
|
|
|
2379
|
-
// Read
|
|
2380
|
-
|
|
2381
|
-
|
|
2377
|
+
// Step 1: Read work items (read-only, no lock) to find plan items that are dispatched.
|
|
2378
|
+
const dispatchedItemIds = new Set();
|
|
2379
|
+
for (const wiPath of wiPaths) {
|
|
2380
|
+
try {
|
|
2381
|
+
const items = safeJsonArr(wiPath);
|
|
2382
|
+
for (const w of items) {
|
|
2383
|
+
if (w.sourcePlan !== body.file) continue;
|
|
2384
|
+
if (w.completedAt || DONE_STATUSES.has(w.status)) continue;
|
|
2385
|
+
if (w.status === WI_STATUS.DISPATCHED && w.id) dispatchedItemIds.add(w.id);
|
|
2386
|
+
}
|
|
2387
|
+
} catch { /* file may not exist */ }
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
// Step 2: Read dispatch.json (read-only, no lock) to collect kill targets.
|
|
2391
|
+
const killTargets = []; // { agent, pid, statusPath }
|
|
2392
|
+
const dispatch = safeJsonObj(dispatchPath);
|
|
2393
|
+
const activeEntries = Array.isArray(dispatch.active) ? dispatch.active : [];
|
|
2394
|
+
for (const d of activeEntries) {
|
|
2395
|
+
const itemId = d.meta?.item?.id;
|
|
2396
|
+
const matchesById = itemId && dispatchedItemIds.has(itemId);
|
|
2397
|
+
const matchesByKey = d.meta?.dispatchKey && [...dispatchedItemIds].some(id => d.meta.dispatchKey.includes(id));
|
|
2398
|
+
if (matchesById || matchesByKey) {
|
|
2399
|
+
const statusPath = path.join(MINIONS_DIR, 'agents', d.agent, 'status.json');
|
|
2382
2400
|
try {
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
if (w.completedAt || DONE_STATUSES.has(w.status)) continue;
|
|
2389
|
-
|
|
2390
|
-
if (w.status === WI_STATUS.DISPATCHED) {
|
|
2391
|
-
// Kill the agent working on this item, if any.
|
|
2392
|
-
const activeEntry = (dispatch.active || []).find(d => d.meta?.item?.id === w.id || d.meta?.dispatchKey?.includes(w.id));
|
|
2393
|
-
if (activeEntry) {
|
|
2394
|
-
const statusPath = path.join(MINIONS_DIR, 'agents', activeEntry.agent, 'status.json');
|
|
2395
|
-
try {
|
|
2396
|
-
const agentStatus = safeJsonObj(statusPath);
|
|
2397
|
-
if (agentStatus.pid) {
|
|
2398
|
-
try {
|
|
2399
|
-
const safePid = shared.validatePid(agentStatus.pid);
|
|
2400
|
-
if (process.platform === 'win32') {
|
|
2401
|
-
require('child_process').execFileSync('taskkill', ['/PID', String(safePid), '/F', '/T'], { stdio: 'pipe', timeout: 5000, windowsHide: true });
|
|
2402
|
-
} else {
|
|
2403
|
-
process.kill(safePid, 'SIGTERM');
|
|
2404
|
-
}
|
|
2405
|
-
} catch { /* process may be dead or invalid PID */ }
|
|
2406
|
-
}
|
|
2407
|
-
agentStatus.status = 'idle';
|
|
2408
|
-
delete agentStatus.currentTask;
|
|
2409
|
-
delete agentStatus.dispatched;
|
|
2410
|
-
safeWrite(statusPath, agentStatus);
|
|
2411
|
-
} catch (e) { console.error('agent reset:', e.message); }
|
|
2412
|
-
killedAgents.add(activeEntry.agent);
|
|
2413
|
-
}
|
|
2414
|
-
}
|
|
2401
|
+
const agentStatus = safeJsonObj(statusPath);
|
|
2402
|
+
killTargets.push({ agent: d.agent, pid: agentStatus.pid || null, statusPath });
|
|
2403
|
+
} catch { killTargets.push({ agent: d.agent, pid: null, statusPath }); }
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2415
2406
|
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
} catch (e) { console.error('reset work items:', e.message); }
|
|
2407
|
+
// Step 3: Kill agent processes OUTSIDE any lock (expensive, may take seconds).
|
|
2408
|
+
const killedAgents = new Set();
|
|
2409
|
+
for (const target of killTargets) {
|
|
2410
|
+
if (target.pid) {
|
|
2411
|
+
try {
|
|
2412
|
+
const safePid = shared.validatePid(target.pid);
|
|
2413
|
+
if (process.platform === 'win32') {
|
|
2414
|
+
require('child_process').execFileSync('taskkill', ['/PID', String(safePid), '/F', '/T'], { stdio: 'pipe', timeout: 5000, windowsHide: true });
|
|
2415
|
+
} else {
|
|
2416
|
+
process.kill(safePid, 'SIGTERM');
|
|
2417
|
+
}
|
|
2418
|
+
} catch { /* process may be dead or invalid PID */ }
|
|
2429
2419
|
}
|
|
2420
|
+
// Reset agent status file (no lock needed — agent-specific file).
|
|
2421
|
+
try {
|
|
2422
|
+
const agentStatus = safeJsonObj(target.statusPath);
|
|
2423
|
+
agentStatus.status = 'idle';
|
|
2424
|
+
delete agentStatus.currentTask;
|
|
2425
|
+
delete agentStatus.dispatched;
|
|
2426
|
+
safeWrite(target.statusPath, agentStatus);
|
|
2427
|
+
} catch (e) { console.error('agent reset:', e.message); }
|
|
2428
|
+
killedAgents.add(target.agent);
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
// Step 4: Mutate work-items.json per path — pause items (each lock held briefly, no nesting).
|
|
2432
|
+
let reset = 0;
|
|
2433
|
+
const resetItemIds = new Set();
|
|
2434
|
+
for (const wiPath of wiPaths) {
|
|
2435
|
+
try {
|
|
2436
|
+
mutateWorkItems(wiPath, items => {
|
|
2437
|
+
for (const w of items) {
|
|
2438
|
+
if (w.sourcePlan !== body.file) continue;
|
|
2439
|
+
if (w.completedAt || DONE_STATUSES.has(w.status)) continue;
|
|
2440
|
+
if (w.status !== WI_STATUS.PAUSED) reset++;
|
|
2441
|
+
w.status = WI_STATUS.PAUSED;
|
|
2442
|
+
w._pausedBy = 'prd-pause';
|
|
2443
|
+
delete w._resumedAt;
|
|
2444
|
+
delete w.dispatched_at;
|
|
2445
|
+
delete w.dispatched_to;
|
|
2446
|
+
delete w.failReason;
|
|
2447
|
+
delete w.failedAt;
|
|
2448
|
+
if (w.id) resetItemIds.add(w.id);
|
|
2449
|
+
}
|
|
2450
|
+
});
|
|
2451
|
+
} catch (e) { console.error('reset work items:', e.message); }
|
|
2452
|
+
}
|
|
2430
2453
|
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2454
|
+
// Step 5: Re-acquire dispatch lock to clean up active entries (brief lock, no nesting).
|
|
2455
|
+
mutateJsonFileLocked(dispatchPath, (dispatchData) => {
|
|
2456
|
+
dispatchData.active = Array.isArray(dispatchData.active) ? dispatchData.active : [];
|
|
2457
|
+
dispatchData.active = dispatchData.active.filter(d => {
|
|
2434
2458
|
const itemId = d.meta?.item?.id;
|
|
2435
2459
|
if (itemId && resetItemIds.has(itemId)) return false;
|
|
2436
2460
|
if (killedAgents.has(d.agent)) return false;
|
|
2437
2461
|
return true;
|
|
2438
2462
|
});
|
|
2439
|
-
return
|
|
2463
|
+
return dispatchData;
|
|
2440
2464
|
}, { defaultValue: { pending: [], active: [], completed: [] } });
|
|
2441
2465
|
|
|
2442
2466
|
invalidateStatusCache();
|
package/engine/dispatch.js
CHANGED
|
@@ -30,7 +30,7 @@ function mutateDispatch(mutator) {
|
|
|
30
30
|
dispatch.pending = Array.isArray(dispatch.pending) ? dispatch.pending : [];
|
|
31
31
|
dispatch.active = Array.isArray(dispatch.active) ? dispatch.active : [];
|
|
32
32
|
dispatch.completed = Array.isArray(dispatch.completed) ? dispatch.completed : [];
|
|
33
|
-
return mutator(dispatch)
|
|
33
|
+
return mutator(dispatch) ?? dispatch;
|
|
34
34
|
}, { defaultValue: defaultDispatch });
|
|
35
35
|
// Invalidate the read cache so next getDispatch() sees fresh data
|
|
36
36
|
try { require('./queries').invalidateDispatchCache(); } catch {}
|
package/engine/lifecycle.js
CHANGED
|
@@ -962,7 +962,7 @@ function findDependentActivePrs(mergedItemId, config) {
|
|
|
962
962
|
for (const p of projects) {
|
|
963
963
|
const prs = safeJson(projectPrPath(p)) || [];
|
|
964
964
|
for (const pr of prs) {
|
|
965
|
-
if (!pr.branch || pr.status !==
|
|
965
|
+
if (!pr.branch || pr.status !== PR_STATUS.ACTIVE) continue;
|
|
966
966
|
const linked = (pr.prdItems || []).some(id => dependentWis.some(wi => wi.id === id));
|
|
967
967
|
if (linked && !results.some(r => r.pr.id === pr.id)) {
|
|
968
968
|
const wi = dependentWis.find(w => (pr.prdItems || []).includes(w.id));
|
|
@@ -1020,25 +1020,31 @@ async function rebaseBranchOntoMain(pr, project, config) {
|
|
|
1020
1020
|
const PENDING_REBASES_PATH = path.join(ENGINE_DIR, 'pending-rebases.json');
|
|
1021
1021
|
|
|
1022
1022
|
function queuePendingRebase(pr, project, mergedItemId) {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1023
|
+
mutateJsonFileLocked(PENDING_REBASES_PATH, (pending) => {
|
|
1024
|
+
if (pending.some(e => e.prId === pr.id)) return; // already queued
|
|
1025
|
+
pending.push({ prId: pr.id, branch: pr.branch, projectName: project.name, mergedItemId, queuedAt: ts(), attempts: 0 });
|
|
1026
|
+
}, { defaultValue: [] });
|
|
1027
1027
|
}
|
|
1028
1028
|
|
|
1029
1029
|
async function processPendingRebases(config) {
|
|
1030
|
-
|
|
1031
|
-
|
|
1030
|
+
// Atomically drain the queue under lock so concurrent queuePendingRebase calls
|
|
1031
|
+
// during processing don't lose entries (they append to the now-empty file).
|
|
1032
|
+
let snapshot = [];
|
|
1033
|
+
mutateJsonFileLocked(PENDING_REBASES_PATH, (data) => {
|
|
1034
|
+
snapshot = [...data];
|
|
1035
|
+
return []; // drain file
|
|
1036
|
+
}, { defaultValue: [] });
|
|
1037
|
+
if (snapshot.length === 0) return;
|
|
1032
1038
|
|
|
1033
1039
|
const remaining = [];
|
|
1034
|
-
for (const entry of
|
|
1040
|
+
for (const entry of snapshot) {
|
|
1035
1041
|
if (isBranchActive(entry.branch)) { remaining.push(entry); continue; }
|
|
1036
1042
|
|
|
1037
1043
|
const project = shared.getProjects(config).find(p => p.name === entry.projectName);
|
|
1038
1044
|
if (!project) continue;
|
|
1039
1045
|
|
|
1040
1046
|
const prs = safeJson(projectPrPath(project)) || [];
|
|
1041
|
-
const pr = prs.find(p => p.id === entry.prId && p.status ===
|
|
1047
|
+
const pr = prs.find(p => p.id === entry.prId && p.status === PR_STATUS.ACTIVE);
|
|
1042
1048
|
if (!pr) continue; // PR closed/merged since queuing
|
|
1043
1049
|
|
|
1044
1050
|
const result = await rebaseBranchOntoMain(pr, project, config);
|
|
@@ -1052,7 +1058,12 @@ async function processPendingRebases(config) {
|
|
|
1052
1058
|
}
|
|
1053
1059
|
}
|
|
1054
1060
|
}
|
|
1055
|
-
|
|
1061
|
+
// Merge remaining items back under lock — entries queued during processing are preserved
|
|
1062
|
+
if (remaining.length > 0) {
|
|
1063
|
+
mutateJsonFileLocked(PENDING_REBASES_PATH, (data) => {
|
|
1064
|
+
data.push(...remaining);
|
|
1065
|
+
}, { defaultValue: [] });
|
|
1066
|
+
}
|
|
1056
1067
|
}
|
|
1057
1068
|
|
|
1058
1069
|
// ─── Post-Merge / Post-Close Hooks ───────────────────────────────────────────
|
package/engine.js
CHANGED
|
@@ -138,7 +138,7 @@ const { renderPlaybook, validatePlaybookVars, PLAYBOOK_REQUIRED_VARS,
|
|
|
138
138
|
const { runPostCompletionHooks, updateWorkItemStatus, syncPrdItemStatus, reconcilePrdStatuses, handlePostMerge, checkPlanCompletion,
|
|
139
139
|
syncPrsFromOutput, updatePrAfterReview, updatePrAfterFix, checkForLearnings, extractSkillsFromOutput,
|
|
140
140
|
updateAgentHistory, updateMetrics, createReviewFeedbackForAuthor, parseAgentOutput, syncPrdFromPrs,
|
|
141
|
-
isItemCompleted, classifyFailure, processPendingRebases } = require('./engine/lifecycle');
|
|
141
|
+
isItemCompleted, classifyFailure, processPendingRebases, resolveWorkItemPath } = require('./engine/lifecycle');
|
|
142
142
|
|
|
143
143
|
// ─── Agent Spawner ──────────────────────────────────────────────────────────
|
|
144
144
|
|
|
@@ -848,7 +848,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
848
848
|
const childEnv = shared.cleanChildEnv();
|
|
849
849
|
let resumeProc;
|
|
850
850
|
try {
|
|
851
|
-
resumeProc = runFile(process.execPath, [spawnScript, steerPromptPath,
|
|
851
|
+
resumeProc = runFile(process.execPath, [spawnScript, steerPromptPath, sysPromptPath, ...resumeArgs], {
|
|
852
852
|
cwd,
|
|
853
853
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
854
854
|
env: childEnv,
|
|
@@ -2485,6 +2485,7 @@ function discoverCentralWorkItems(config) {
|
|
|
2485
2485
|
|
|
2486
2486
|
const ap = assignedProject || (projects.length > 0 ? projects[0] : null);
|
|
2487
2487
|
if (!ap) { log('warn', `Fan-out: skipping ${fanKey} — no projects configured`); continue; }
|
|
2488
|
+
const fanBranch = `fan/${item.id}/${agent.id}`;
|
|
2488
2489
|
const vars = {
|
|
2489
2490
|
...buildBaseVars(agent.id, config, ap),
|
|
2490
2491
|
item_id: item.id,
|
|
@@ -2495,6 +2496,7 @@ function discoverCentralWorkItems(config) {
|
|
|
2495
2496
|
additional_context: item.prompt ? `## Additional Context\n\n${item.prompt}` : '',
|
|
2496
2497
|
scope_section: buildProjectContext(projects, assignedProject, true, agent.name, agent.role),
|
|
2497
2498
|
project_path: ap?.localPath || '',
|
|
2499
|
+
branch_name: fanBranch,
|
|
2498
2500
|
};
|
|
2499
2501
|
|
|
2500
2502
|
// Build common vars: references, acceptance criteria, notes (ASK only), task context
|
|
@@ -2519,7 +2521,7 @@ function discoverCentralWorkItems(config) {
|
|
|
2519
2521
|
prompt,
|
|
2520
2522
|
meta: {
|
|
2521
2523
|
dispatchKey: fanKey, source: 'central-work-item-fanout', item, parentKey: key,
|
|
2522
|
-
branch:
|
|
2524
|
+
branch: fanBranch,
|
|
2523
2525
|
deadline: item.timeout ? Date.now() + item.timeout : Date.now() + (config.engine?.fanOutTimeout || config.engine?.agentTimeout || DEFAULTS.agentTimeout)
|
|
2524
2526
|
}
|
|
2525
2527
|
});
|
|
@@ -2564,6 +2566,7 @@ function discoverCentralWorkItems(config) {
|
|
|
2564
2566
|
additional_context: item.prompt ? `## Additional Context\n\n${item.prompt}` : '',
|
|
2565
2567
|
scope_section: buildProjectContext(projects, null, false, agentName, agentRole),
|
|
2566
2568
|
project_path: firstProject?.localPath || '',
|
|
2569
|
+
branch_name: centralBranch,
|
|
2567
2570
|
};
|
|
2568
2571
|
const centralWtPath = firstProject?.localPath
|
|
2569
2572
|
? path.resolve(firstProject.localPath, config.engine?.worktreeRoot || '../worktrees', centralBranch)
|
|
@@ -2944,6 +2947,10 @@ async function tickInner() {
|
|
|
2944
2947
|
for (const project of projects) {
|
|
2945
2948
|
try {
|
|
2946
2949
|
const wiPath = projectWorkItemsPath(project);
|
|
2950
|
+
// Collect keys to clear AFTER work-items lock is released (avoid nested locks)
|
|
2951
|
+
const dispatchKeysToClear = [];
|
|
2952
|
+
const cooldownKeysToClear = [];
|
|
2953
|
+
|
|
2947
2954
|
mutateWorkItems(wiPath, items => {
|
|
2948
2955
|
let changed = false;
|
|
2949
2956
|
const failedIds = new Set(items.filter(w => w.status === WI_STATUS.FAILED).map(w => w.id));
|
|
@@ -2968,23 +2975,10 @@ async function tickInner() {
|
|
|
2968
2975
|
delete item.dispatched_to;
|
|
2969
2976
|
changed = true;
|
|
2970
2977
|
|
|
2971
|
-
//
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
dp.completed = dp.completed.filter(d => d.meta?.dispatchKey !== key);
|
|
2976
|
-
return dp;
|
|
2977
|
-
});
|
|
2978
|
-
} catch (e) { log('warn', 'stall recovery clear dispatch: ' + e.message); }
|
|
2979
|
-
|
|
2980
|
-
// Clear cooldown so item isn't blocked by exponential backoff
|
|
2981
|
-
try {
|
|
2982
|
-
const key = `work-${project.name}-${item.id}`;
|
|
2983
|
-
if (dispatchCooldowns.has(key)) {
|
|
2984
|
-
dispatchCooldowns.delete(key);
|
|
2985
|
-
saveCooldowns();
|
|
2986
|
-
}
|
|
2987
|
-
} catch (e) { log('warn', 'stall recovery clear cooldown: ' + e.message); }
|
|
2978
|
+
// Collect dispatch + cooldown keys for clearing outside lock
|
|
2979
|
+
const key = `work-${project.name}-${item.id}`;
|
|
2980
|
+
dispatchKeysToClear.push(key);
|
|
2981
|
+
cooldownKeysToClear.push(key);
|
|
2988
2982
|
}
|
|
2989
2983
|
}
|
|
2990
2984
|
|
|
@@ -3002,19 +2996,33 @@ async function tickInner() {
|
|
|
3002
2996
|
delete dep.failedAt;
|
|
3003
2997
|
delete dep.dispatched_at;
|
|
3004
2998
|
delete dep.dispatched_to;
|
|
3005
|
-
//
|
|
3006
|
-
|
|
3007
|
-
const key = `work-${project.name}-${dep.id}`;
|
|
3008
|
-
mutateDispatch((dp) => {
|
|
3009
|
-
dp.completed = dp.completed.filter(d => d.meta?.dispatchKey !== key);
|
|
3010
|
-
return dp;
|
|
3011
|
-
});
|
|
3012
|
-
} catch (e) { log('warn', 'stall recovery clear dependent dispatch: ' + e.message); }
|
|
2999
|
+
// Collect dispatch key for clearing outside lock
|
|
3000
|
+
dispatchKeysToClear.push(`work-${project.name}-${dep.id}`);
|
|
3013
3001
|
}
|
|
3014
3002
|
}
|
|
3015
3003
|
}
|
|
3016
3004
|
}
|
|
3017
3005
|
});
|
|
3006
|
+
|
|
3007
|
+
// Clear dispatch entries AFTER work-items lock is released (no nested locks)
|
|
3008
|
+
for (const key of dispatchKeysToClear) {
|
|
3009
|
+
try {
|
|
3010
|
+
mutateDispatch((dp) => {
|
|
3011
|
+
dp.completed = dp.completed.filter(d => d.meta?.dispatchKey !== key);
|
|
3012
|
+
return dp;
|
|
3013
|
+
});
|
|
3014
|
+
} catch (e) { log('warn', 'stall recovery clear dispatch: ' + e.message); }
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
// Clear cooldowns AFTER work-items lock is released
|
|
3018
|
+
for (const key of cooldownKeysToClear) {
|
|
3019
|
+
try {
|
|
3020
|
+
if (dispatchCooldowns.has(key)) {
|
|
3021
|
+
dispatchCooldowns.delete(key);
|
|
3022
|
+
saveCooldowns();
|
|
3023
|
+
}
|
|
3024
|
+
} catch (e) { log('warn', 'stall recovery clear cooldown: ' + e.message); }
|
|
3025
|
+
}
|
|
3018
3026
|
} catch (e) { log('warn', 'stall recovery process project: ' + e.message); }
|
|
3019
3027
|
}
|
|
3020
3028
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.910",
|
|
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"
|